diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 66834d177..cc300711a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,644 +1,643 @@ # SPDX-FileCopyrightText: none # SPDX-License-Identifier: BSD-3-Clause add_subdirectory(icons) add_subdirectory(mimetypes) include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) if (NOT DISABLE_KWATCHGNUPG) add_subdirectory(kwatchgnupg) endif() add_subdirectory(libkleopatraclient) add_subdirectory(conf) add_subdirectory(kconf_update) if(WIN32) set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_win.cpp) set(_kleopatra_extra_SRCS selftest/registrycheck.cpp selftest/registrycheck.h utils/gnupg-registry.c utils/userinfo_win.cpp utils/windowsprocessdevice.cpp utils/windowsprocessdevice.h versioninfo.rc kleopatra.w32-manifest ) else() set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_unix.cpp) set(_kleopatra_extra_SRCS) endif() set(_kleopatra_uiserver_SRCS ${_kleopatra_extra_uiserver_SRCS} selftest/uiservercheck.cpp selftest/uiservercheck.h uiserver/assuanserverconnection.cpp uiserver/assuanserverconnection.h uiserver/createchecksumscommand.cpp uiserver/createchecksumscommand.h uiserver/decryptverifycommandemailbase.cpp uiserver/decryptverifycommandemailbase.h uiserver/decryptverifycommandfilesbase.cpp uiserver/decryptverifycommandfilesbase.h uiserver/echocommand.cpp uiserver/echocommand.h uiserver/encryptcommand.cpp uiserver/encryptcommand.h uiserver/importfilescommand.cpp uiserver/importfilescommand.h uiserver/prepencryptcommand.cpp uiserver/prepencryptcommand.h uiserver/prepsigncommand.cpp uiserver/prepsigncommand.h uiserver/selectcertificatecommand.cpp uiserver/sessiondata.cpp uiserver/sessiondata.h uiserver/signcommand.cpp uiserver/signcommand.h uiserver/signencryptfilescommand.cpp uiserver/uiserver.cpp uiserver/verifychecksumscommand.cpp uiserver/verifychecksumscommand.h ) set(_kleopatra_uiserver_extra_libs LibAssuan::LibAssuan LibGpgError::LibGpgError) if(HAVE_GPG_ERR_SOURCE_KLEO) add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO) add_definitions(-DGPGMEPP_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO) else() add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1) add_definitions(-DGPGMEPP_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1) endif() if(KPim${KF_MAJOR_VERSION}IdentityManagement_FOUND AND KPim${KF_MAJOR_VERSION}MailTransport_FOUND AND KPim${KF_MAJOR_VERSION}AkonadiMime_FOUND) set(_kleopatra_mail_libs KPim${KF_MAJOR_VERSION}::IdentityManagement # Export OpenPGP keys using WKS KPim${KF_MAJOR_VERSION}::MailTransport KPim${KF_MAJOR_VERSION}::AkonadiMime ) add_definitions(-DMAILAKONADI_ENABLED) endif() ki18n_wrap_ui(_kleopatra_uiserver_SRCS crypto/gui/signingcertificateselectionwidget.ui) set(_kleopatra_SRCS ${_kleopatra_extra_SRCS} accessibility/accessiblelink.cpp accessibility/accessiblelink_p.h accessibility/accessiblerichtextlabel.cpp accessibility/accessiblerichtextlabel_p.h accessibility/accessiblevaluelabel.cpp accessibility/accessiblevaluelabel_p.h accessibility/accessiblewidgetfactory.cpp accessibility/accessiblewidgetfactory.h commands/addsubkeycommand.cpp commands/addsubkeycommand.h commands/adduseridcommand.cpp commands/adduseridcommand.h commands/authenticatepivcardapplicationcommand.cpp commands/authenticatepivcardapplicationcommand.h commands/cardcommand.cpp commands/cardcommand.h commands/certificatetopivcardcommand.cpp commands/certificatetopivcardcommand.h commands/certifycertificatecommand.cpp commands/certifycertificatecommand.h commands/certifygroupcommand.cpp commands/certifygroupcommand.h commands/changeexpirycommand.cpp commands/changeexpirycommand.h commands/changeownertrustcommand.cpp commands/changeownertrustcommand.h commands/changepassphrasecommand.cpp commands/changepassphrasecommand.h commands/changepincommand.cpp commands/changepincommand.h commands/changeroottrustcommand.cpp commands/changeroottrustcommand.h commands/checksumcreatefilescommand.cpp commands/checksumcreatefilescommand.h commands/checksumverifyfilescommand.cpp commands/checksumverifyfilescommand.h commands/clearcrlcachecommand.cpp commands/clearcrlcachecommand.h commands/command.cpp commands/command.h commands/createcsrforcardkeycommand.cpp commands/createcsrforcardkeycommand.h commands/creategroupcommand.cpp commands/creategroupcommand.h commands/createopenpgpkeyfromcardkeyscommand.cpp commands/createopenpgpkeyfromcardkeyscommand.h commands/decryptverifyclipboardcommand.cpp commands/decryptverifyclipboardcommand.h commands/decryptverifyfilescommand.cpp commands/decryptverifyfilescommand.h commands/deletecertificatescommand.cpp commands/deletecertificatescommand.h commands/detailscommand.cpp commands/detailscommand.h commands/dumpcertificatecommand.cpp commands/dumpcertificatecommand.h commands/dumpcrlcachecommand.cpp commands/dumpcrlcachecommand.h commands/encryptclipboardcommand.cpp commands/encryptclipboardcommand.h commands/exportcertificatecommand.cpp commands/exportcertificatecommand.h commands/exportgroupscommand.cpp commands/exportgroupscommand.h commands/exportopenpgpcertstoservercommand.cpp commands/exportopenpgpcertstoservercommand.h commands/exportopenpgpcerttoprovidercommand.cpp commands/exportopenpgpcerttoprovidercommand.h commands/exportpaperkeycommand.cpp commands/exportpaperkeycommand.h commands/exportsecretkeycommand.cpp commands/exportsecretkeycommand.h commands/exportsecretsubkeycommand.cpp commands/exportsecretsubkeycommand.h commands/genrevokecommand.cpp commands/genrevokecommand.h commands/gnupgprocesscommand.cpp commands/gnupgprocesscommand.h commands/importcertificatefromclipboardcommand.cpp commands/importcertificatefromclipboardcommand.h commands/importcertificatefromdatacommand.cpp commands/importcertificatefromdatacommand.h commands/importcertificatefromfilecommand.cpp commands/importcertificatefromfilecommand.h commands/importcertificatefromkeyservercommand.cpp commands/importcertificatefromkeyservercommand.h commands/importcertificatefrompivcardcommand.cpp commands/importcertificatefrompivcardcommand.h commands/importcertificatescommand.cpp commands/importcertificatescommand.h commands/importcrlcommand.cpp commands/importcrlcommand.h commands/importpaperkeycommand.cpp commands/importpaperkeycommand.h commands/keytocardcommand.cpp commands/keytocardcommand.h commands/lookupcertificatescommand.cpp commands/lookupcertificatescommand.h commands/newcertificatesigningrequestcommand.cpp commands/newcertificatesigningrequestcommand.h commands/newopenpgpcertificatecommand.cpp commands/newopenpgpcertificatecommand.h commands/openpgpgeneratecardkeycommand.cpp commands/openpgpgeneratecardkeycommand.h commands/pivgeneratecardkeycommand.cpp commands/pivgeneratecardkeycommand.h commands/refreshcertificatescommand.cpp commands/refreshcertificatescommand.h commands/refreshopenpgpcertscommand.cpp commands/refreshopenpgpcertscommand.h commands/refreshx509certscommand.cpp commands/refreshx509certscommand.h commands/reloadkeyscommand.cpp commands/reloadkeyscommand.h commands/revokecertificationcommand.cpp commands/revokecertificationcommand.h commands/revokekeycommand.cpp commands/revokekeycommand.h commands/revokeuseridcommand.cpp commands/revokeuseridcommand.h commands/selftestcommand.cpp commands/selftestcommand.h commands/setinitialpincommand.cpp commands/setinitialpincommand.h commands/setpivcardapplicationadministrationkeycommand.cpp commands/setpivcardapplicationadministrationkeycommand.h commands/setprimaryuseridcommand.cpp commands/setprimaryuseridcommand.h commands/signclipboardcommand.cpp commands/signclipboardcommand.h commands/signencryptfilescommand.cpp commands/signencryptfilescommand.h commands/signencryptfoldercommand.cpp commands/signencryptfoldercommand.h commands/viewemailfilescommand.cpp commands/viewemailfilescommand.h conf/configuredialog.cpp conf/configuredialog.h conf/groupsconfigdialog.cpp conf/groupsconfigdialog.h conf/groupsconfigwidget.cpp conf/groupsconfigwidget.h crypto/autodecryptverifyfilescontroller.cpp crypto/autodecryptverifyfilescontroller.h crypto/certificateresolver.cpp crypto/certificateresolver.h crypto/checksumsutils_p.cpp crypto/checksumsutils_p.h crypto/controller.cpp crypto/controller.h crypto/createchecksumscontroller.cpp crypto/createchecksumscontroller.h crypto/decryptverifyemailcontroller.cpp crypto/decryptverifyemailcontroller.h crypto/decryptverifyfilescontroller.cpp crypto/decryptverifyfilescontroller.h crypto/decryptverifytask.cpp crypto/decryptverifytask.h crypto/encryptemailcontroller.cpp crypto/encryptemailcontroller.h crypto/encryptemailtask.cpp crypto/encryptemailtask.h crypto/gui/certificatelineedit.cpp crypto/gui/certificatelineedit.h crypto/gui/certificateselectionline.cpp crypto/gui/certificateselectionline.h crypto/gui/decryptverifyfilesdialog.cpp crypto/gui/decryptverifyfilesdialog.h crypto/gui/decryptverifyfileswizard.cpp crypto/gui/decryptverifyfileswizard.h crypto/gui/decryptverifyoperationwidget.cpp crypto/gui/decryptverifyoperationwidget.h crypto/gui/encryptemailwizard.cpp crypto/gui/encryptemailwizard.h crypto/gui/newresultpage.cpp crypto/gui/newresultpage.h crypto/gui/objectspage.cpp crypto/gui/objectspage.h crypto/gui/resolverecipientspage.cpp crypto/gui/resolverecipientspage.h crypto/gui/resultitemwidget.cpp crypto/gui/resultitemwidget.h crypto/gui/resultlistwidget.cpp crypto/gui/resultlistwidget.h crypto/gui/resultpage.cpp crypto/gui/resultpage.h crypto/gui/signemailwizard.cpp crypto/gui/signemailwizard.h crypto/gui/signencryptemailconflictdialog.cpp crypto/gui/signencryptemailconflictdialog.h crypto/gui/signencryptfileswizard.cpp crypto/gui/signencryptfileswizard.h crypto/gui/signencryptwidget.cpp crypto/gui/signencryptwidget.h crypto/gui/signencryptwizard.cpp crypto/gui/signencryptwizard.h crypto/gui/signerresolvepage.cpp crypto/gui/signerresolvepage.h crypto/gui/signingcertificateselectiondialog.cpp crypto/gui/signingcertificateselectiondialog.h crypto/gui/signingcertificateselectionwidget.cpp crypto/gui/signingcertificateselectionwidget.h crypto/gui/unknownrecipientwidget.cpp crypto/gui/unknownrecipientwidget.h crypto/gui/verifychecksumsdialog.cpp crypto/gui/verifychecksumsdialog.h crypto/gui/wizard.cpp crypto/gui/wizard.h crypto/gui/wizardpage.cpp crypto/gui/wizardpage.h crypto/newsignencryptemailcontroller.cpp crypto/newsignencryptemailcontroller.h crypto/recipient.cpp crypto/recipient.h crypto/sender.cpp crypto/sender.h crypto/signemailcontroller.cpp crypto/signemailcontroller.h crypto/signemailtask.cpp crypto/signemailtask.h crypto/signencryptfilescontroller.cpp crypto/signencryptfilescontroller.h crypto/signencrypttask.cpp crypto/signencrypttask.h crypto/task.cpp crypto/task.h crypto/taskcollection.cpp crypto/taskcollection.h crypto/verifychecksumscontroller.cpp crypto/verifychecksumscontroller.h dialogs/addsubkeydialog.cpp dialogs/addsubkeydialog.h dialogs/adduseriddialog.cpp dialogs/adduseriddialog.h dialogs/animatedexpander.cpp dialogs/animatedexpander.h dialogs/cardinfotab.cpp dialogs/cardinfotab.h dialogs/certificatedetailsdialog.cpp dialogs/certificatedetailsdialog.h dialogs/certificatedetailsinputwidget.cpp dialogs/certificatedetailsinputwidget.h dialogs/certificatedetailswidget.cpp dialogs/certificatedetailswidget.h dialogs/certificatedumpwidget.cpp dialogs/certificatedumpwidget.h dialogs/certificateselectiondialog.cpp dialogs/certificateselectiondialog.h dialogs/certifycertificatedialog.cpp dialogs/certifycertificatedialog.h dialogs/certifywidget.cpp dialogs/certifywidget.h dialogs/createcsrforcardkeydialog.cpp dialogs/createcsrforcardkeydialog.h dialogs/deletecertificatesdialog.cpp dialogs/deletecertificatesdialog.h dialogs/editgroupdialog.cpp dialogs/editgroupdialog.h dialogs/expirydialog.cpp dialogs/expirydialog.h dialogs/exportdialog.cpp dialogs/exportdialog.h dialogs/gencardkeydialog.cpp dialogs/gencardkeydialog.h dialogs/groupdetailsdialog.cpp dialogs/groupdetailsdialog.h dialogs/lookupcertificatesdialog.cpp dialogs/lookupcertificatesdialog.h dialogs/nameandemailwidget.cpp dialogs/nameandemailwidget.h dialogs/newopenpgpcertificatedetailsdialog.cpp dialogs/newopenpgpcertificatedetailsdialog.h dialogs/pivcardapplicationadministrationkeyinputdialog.cpp dialogs/pivcardapplicationadministrationkeyinputdialog.h dialogs/revokekeydialog.cpp dialogs/revokekeydialog.h dialogs/selftestdialog.cpp dialogs/selftestdialog.h dialogs/setinitialpindialog.cpp dialogs/setinitialpindialog.h dialogs/subkeyswidget.cpp dialogs/subkeyswidget.h dialogs/trustchainwidget.cpp dialogs/trustchainwidget.h dialogs/updatenotification.cpp dialogs/updatenotification.h dialogs/useridswidget.cpp dialogs/useridswidget.h dialogs/weboftrustwidget.cpp dialogs/weboftrustwidget.h interfaces/anchorprovider.h interfaces/focusfirstchild.h newcertificatewizard/advancedsettingsdialog.cpp newcertificatewizard/advancedsettingsdialog_p.h newcertificatewizard/enterdetailspage.cpp newcertificatewizard/enterdetailspage_p.h newcertificatewizard/keyalgo.cpp newcertificatewizard/keyalgo_p.h newcertificatewizard/keycreationpage.cpp newcertificatewizard/keycreationpage_p.h newcertificatewizard/listwidget.cpp newcertificatewizard/listwidget.h newcertificatewizard/newcertificatewizard.cpp newcertificatewizard/newcertificatewizard.h newcertificatewizard/resultpage.cpp newcertificatewizard/resultpage_p.h newcertificatewizard/wizardpage.cpp newcertificatewizard/wizardpage_p.h selftest/compliancecheck.cpp selftest/compliancecheck.h selftest/enginecheck.cpp selftest/enginecheck.h selftest/gpgagentcheck.cpp selftest/gpgagentcheck.h selftest/gpgconfcheck.cpp selftest/gpgconfcheck.h selftest/libkleopatrarccheck.cpp selftest/libkleopatrarccheck.h selftest/selftest.cpp selftest/selftest.h smartcard/algorithminfo.h smartcard/card.cpp smartcard/card.h smartcard/deviceinfowatcher.cpp smartcard/deviceinfowatcher.h smartcard/keypairinfo.cpp smartcard/keypairinfo.h smartcard/netkeycard.cpp smartcard/netkeycard.h smartcard/openpgpcard.cpp smartcard/openpgpcard.h smartcard/p15card.cpp smartcard/p15card.h smartcard/pivcard.cpp smartcard/pivcard.h smartcard/readerstatus.cpp smartcard/readerstatus.h smartcard/utils.cpp smartcard/utils.h utils/accessibility.cpp utils/accessibility.h utils/action_data.cpp utils/action_data.h utils/applicationstate.cpp utils/applicationstate.h utils/archivedefinition.cpp utils/archivedefinition.h utils/certificatepair.h utils/clipboardmenu.cpp utils/clipboardmenu.h utils/debug-helpers.cpp utils/debug-helpers.h utils/dragqueen.cpp utils/dragqueen.h utils/email.cpp utils/email.h utils/emptypassphraseprovider.cpp utils/emptypassphraseprovider.h utils/expiration.cpp utils/expiration.h utils/filedialog.cpp utils/filedialog.h utils/gui-helper.cpp utils/gui-helper.h utils/headerview.cpp utils/headerview.h utils/input.cpp utils/input.h utils/iodevicelogger.cpp utils/iodevicelogger.h utils/kdpipeiodevice.cpp utils/kdpipeiodevice.h utils/keyexportdraghandler.cpp utils/keyexportdraghandler.h utils/keyparameters.cpp utils/keyparameters.h utils/kuniqueservice.cpp utils/kuniqueservice.h utils/log.cpp utils/log.h utils/memory-helpers.h utils/migration.cpp utils/migration.h utils/multivalidator.cpp utils/multivalidator.h utils/output.cpp utils/output.h utils/overwritedialog.cpp utils/overwritedialog.h utils/path-helper.cpp utils/path-helper.h utils/scrollarea.cpp utils/scrollarea.h utils/systemtrayicon.cpp utils/systemtrayicon.h utils/tags.cpp utils/tags.h utils/types.cpp utils/types.h utils/userinfo.cpp utils/userinfo.h utils/validation.cpp utils/validation.h utils/writecertassuantransaction.cpp utils/writecertassuantransaction.h utils/wsastarter.cpp utils/wsastarter.h view/anchorcache.cpp view/anchorcache_p.h view/cardkeysview.cpp view/cardkeysview.h view/errorlabel.cpp view/errorlabel.h view/formtextinput.cpp view/formtextinput.h view/htmllabel.cpp view/htmllabel.h view/infofield.cpp view/infofield.h view/keycacheoverlay.cpp view/keycacheoverlay.h view/keylistcontroller.cpp view/keylistcontroller.h view/keytreeview.cpp view/keytreeview.h view/netkeywidget.cpp view/netkeywidget.h view/nullpinwidget.cpp view/nullpinwidget.h view/openpgpkeycardwidget.cpp view/openpgpkeycardwidget.h view/overlaywidget.cpp view/overlaywidget.h view/p15cardwidget.cpp view/p15cardwidget.h view/padwidget.cpp view/padwidget.h view/pgpcardwidget.cpp view/pgpcardwidget.h view/pivcardwidget.cpp view/pivcardwidget.h view/progressoverlay.cpp view/progressoverlay.h view/searchbar.cpp view/searchbar.h view/smartcardwidget.cpp view/smartcardwidget.h view/tabwidget.cpp view/tabwidget.h view/textoverlay.cpp view/textoverlay.h view/urllabel.cpp view/urllabel.h view/waitwidget.cpp view/waitwidget.h view/welcomewidget.cpp view/welcomewidget.h aboutdata.cpp aboutdata.h kleopatra.qrc kleopatraapplication.cpp kleopatraapplication.h main.cpp mainwindow.cpp mainwindow.h systrayicon.cpp systrayicon.h ) if(WIN32) configure_file (versioninfo.rc.in versioninfo.rc) set(_kleopatra_SRCS ${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc ${_kleopatra_SRCS}) configure_file (kleopatra.w32-manifest.in kleopatra.w32-manifest) set(_kleopatra_SRCS ${CMAKE_CURRENT_BINARY_DIR}/kleopatra.w32-manifest ${_kleopatra_SRCS}) endif() set (_kleopatra_SRCS conf/kleopageconfigdialog.cpp conf/kleopageconfigdialog.h ${_kleopatra_SRCS}) ecm_qt_declare_logging_category(_kleopatra_SRCS HEADER kleopatra_debug.h IDENTIFIER KLEOPATRA_LOG CATEGORY_NAME org.kde.pim.kleopatra DESCRIPTION "kleopatra (kleopatra)" OLD_CATEGORY_NAMES log_kleopatra EXPORT KLEOPATRA ) if(KLEO_MODEL_TEST) add_definitions(-DKLEO_MODEL_TEST) set(_kleopatra_SRCS ${_kleopatra_SRCS} models/modeltest.cpp) endif() ki18n_wrap_ui(_kleopatra_SRCS dialogs/setinitialpindialog.ui - dialogs/trustchainwidget.ui newcertificatewizard/listwidget.ui ) kconfig_add_kcfg_files(_kleopatra_SRCS kcfg/emailoperationspreferences.kcfgc kcfg/fileoperationspreferences.kcfgc kcfg/settings.kcfgc kcfg/smimevalidationpreferences.kcfgc kcfg/tagspreferences.kcfgc kcfg/tooltippreferences.kcfgc ) file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/*-apps-kleopatra.png") ecm_add_app_icon(_kleopatra_SRCS ICONS ${ICONS_SRCS}) add_executable(kleopatra_bin ${_kleopatra_SRCS} ${_kleopatra_uiserver_SRCS}) # For the ConfigureDialog & KCMs target_link_libraries(kleopatra_bin kcm_kleopatra_static) #if (COMPILE_WITH_UNITY_CMAKE_SUPPORT) # set_target_properties(kleopatra_bin PROPERTIES UNITY_BUILD ON) #endif() set_target_properties(kleopatra_bin PROPERTIES OUTPUT_NAME kleopatra) if (WIN32) set(_kleopatra_platform_libs "secur32") endif () target_link_libraries(kleopatra_bin Gpgmepp KPim${KF_MAJOR_VERSION}::Libkleo KPim${KF_MAJOR_VERSION}::Mime KPim${KF_MAJOR_VERSION}::MimeTreeParserWidgets KF${KF_MAJOR_VERSION}::Codecs KF${KF_MAJOR_VERSION}::CoreAddons KF${KF_MAJOR_VERSION}::Crash KF${KF_MAJOR_VERSION}::I18n KF${KF_MAJOR_VERSION}::IconThemes KF${KF_MAJOR_VERSION}::ItemModels KF${KF_MAJOR_VERSION}::KIOCore KF${KF_MAJOR_VERSION}::KIOWidgets KF${KF_MAJOR_VERSION}::WindowSystem KF${KF_MAJOR_VERSION}::XmlGui Qt::Network Qt::PrintSupport # Printing secret keys kleopatraclientcore ${_kleopatra_extra_libs} ${_kleopatra_mail_libs} ${_kleopatra_uiserver_extra_libs} ${_kleopatra_dbusaddons_libs} ${_kleopatra_platform_libs} ) if (QT_MAJOR_VERSION STREQUAL "6") target_link_libraries(kleopatra_bin Qt6::Core5Compat) target_link_libraries(kleopatra_bin QGpgmeQt6) else() target_link_libraries(kleopatra_bin QGpgme) endif() install(TARGETS kleopatra_bin ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install( PROGRAMS data/org.kde.kleopatra.desktop data/kleopatra_import.desktop DESTINATION ${KDE_INSTALL_APPDIR} ) install(FILES data/org.kde.kleopatra.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install(FILES data/kleopatra-mime.xml DESTINATION ${KDE_INSTALL_MIMEDIR}) install( PROGRAMS data/kleopatra_signencryptfiles.desktop data/kleopatra_signencryptfolders.desktop data/kleopatra_decryptverifyfiles.desktop data/kleopatra_decryptverifyfolders.desktop DESTINATION ${KDE_INSTALL_DATADIR}/kio/servicemenus ) diff --git a/src/dialogs/cardinfotab.cpp b/src/dialogs/cardinfotab.cpp index 49070cea8..2c41a0e83 100644 --- a/src/dialogs/cardinfotab.cpp +++ b/src/dialogs/cardinfotab.cpp @@ -1,181 +1,177 @@ // SPDX-FileCopyrightText: 2024 g10 Code GmbH // SPDX-FileContributor: Tobias Fella // SPDX-License-Identifier: GPL-2.0-or-later #include "cardinfotab.h" #include "smartcard/card.h" #include "smartcard/readerstatus.h" #include "view/smartcardwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace SmartCard; class CardInfoTab::Private { friend class ::Kleo::CardInfoTab; CardInfoTab *const q; void loadData(); private: GpgME::Key key; TreeWidget *subkeysTree = nullptr; QLabel *placeholderLabel = nullptr; QPushButton *reloadButton = nullptr; public: Private(CardInfoTab *qq) : q{qq} { auto vLay = new QVBoxLayout(q); vLay->setContentsMargins({}); - vLay->setSpacing(0); subkeysTree = new TreeWidget{q}; subkeysTree->setAccessibleName(i18n("Subkeys")); subkeysTree->setAllColumnsShowFocus(false); subkeysTree->setSelectionMode(QAbstractItemView::SingleSelection); subkeysTree->setRootIsDecorated(false); subkeysTree->setHeaderLabels({ i18nc("@title:column", "Keygrip"), i18nc("@title:column", "Token"), i18nc("@title:column", "Type"), i18nc("@title:column", "Serial Number"), i18nc("@title:column", "Owner"), }); vLay->addWidget(subkeysTree); placeholderLabel = new QLabel(i18nc("@info", "Smartcard information is only available for your own certificates.")); placeholderLabel->setVisible(false); placeholderLabel->setAlignment(Qt::AlignHCenter); vLay->addWidget(placeholderLabel); - auto separator = new KSeparator(q); - vLay->addWidget(separator); - auto bbox = new QHBoxLayout; reloadButton = new QPushButton(i18nc("@action:button", "Reload")); bbox->addWidget(reloadButton); bbox->addStretch(1); vLay->addLayout(bbox); } }; CardInfoTab::CardInfoTab(QWidget *parent) : QWidget{parent} , d{std::make_unique(this)} { connect(d->reloadButton, &QPushButton::clicked, this, []() { ReaderStatus::mutableInstance()->updateStatus(); }); connect(ReaderStatus::instance(), &ReaderStatus::cardAdded, this, [this]() { d->loadData(); }); connect(ReaderStatus::instance(), &ReaderStatus::cardChanged, this, [this]() { d->loadData(); }); connect(ReaderStatus::instance(), &ReaderStatus::cardRemoved, this, [this]() { d->loadData(); }); } CardInfoTab::~CardInfoTab() = default; GpgME::Key CardInfoTab::key() const { return d->key; } void CardInfoTab::Private::loadData() { subkeysTree->clear(); for (const auto &subkey : key.subkeys()) { const auto &cards = KeyCache::instance()->cardsForSubkey(subkey); for (const auto &info : cards) { auto availableCard = SmartCard::ReaderStatus::instance()->getCardWithKeyRef(info.serialNumber.toStdString(), info.keyRef.toStdString()); auto item = new QTreeWidgetItem; item->setData(0, Qt::DisplayRole, QString::fromLatin1(subkey.keyGrip())); item->setData(1, Qt::DisplayRole, info.serialNumber); if (availableCard) { const QString manufacturer = QString::fromStdString(availableCard->manufacturer()); const bool manufacturerIsUnknown = manufacturer.isEmpty() || manufacturer == QLatin1String("unknown"); item->setData( 2, Qt::DisplayRole, manufacturerIsUnknown ? i18nc("Unknown (card)", "Unknown %1 v%2", availableCard->displayAppName(), availableCard->displayAppVersion()) : i18nc(" ", "%1 %2 v%3", manufacturer, availableCard->displayAppName(), availableCard->displayAppVersion())); item->setData(3, Qt::DisplayRole, availableCard->displaySerialNumber()); item->setData(4, Qt::DisplayRole, availableCard->cardHolder().size() > 0 ? availableCard->cardHolder() : i18nc("unknown cardholder", "unknown")); item->setData(5, Qt::UserRole, QString::fromStdString(availableCard->appName())); } else { item->setData(2, Qt::DisplayRole, i18n("n/a")); if (!info.displaySerialNumber.isEmpty()) { item->setData(3, Qt::DisplayRole, info.displaySerialNumber); } else { item->setData(3, Qt::DisplayRole, i18n("n/a")); } item->setData(4, Qt::DisplayRole, i18n("n/a")); } subkeysTree->addTopLevelItem(item); } } for (int i = 0; i < subkeysTree->columnCount(); i++) { subkeysTree->resizeColumnToContents(i); } } void CardInfoTab::setKey(const GpgME::Key &key) { if (!key.hasSecret()) { d->subkeysTree->setVisible(false); d->placeholderLabel->setVisible(true); d->reloadButton->setEnabled(false); return; } d->key = key; d->subkeysTree->header()->resizeSections(QHeaderView::ResizeToContents); d->subkeysTree->restoreColumnLayout(QStringLiteral("CardInfoTab")); d->loadData(); for (int i = 0; i < d->subkeysTree->columnCount(); i++) { d->subkeysTree->resizeColumnToContents(i); } } #include "moc_cardinfotab.cpp" diff --git a/src/dialogs/certificatedetailswidget.cpp b/src/dialogs/certificatedetailswidget.cpp index bbf9c5e4a..9026933f1 100644 --- a/src/dialogs/certificatedetailswidget.cpp +++ b/src/dialogs/certificatedetailswidget.cpp @@ -1,679 +1,684 @@ /* 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 "cardinfotab.h" #include "certificatedumpwidget.h" #include "dialogs/weboftrustwidget.h" #include "kleopatra_debug.h" #include "subkeyswidget.h" #include "trustchainwidget.h" #include "useridswidget.h" #include "commands/changeexpirycommand.h" #include "commands/detailscommand.h" #include "utils/accessibility.h" #include "utils/tags.h" #include "view/infofield.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 +#include #include #include #include #include #if __has_include() #include #if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L #define USE_RANGES #endif #endif #include Q_DECLARE_METATYPE(GpgME::UserID) using namespace Kleo; class CertificateDetailsWidget::Private { public: Private(CertificateDetailsWidget *qq); void setupCommonProperties(); void setUpSMIMEAdressList(); void setupPGPProperties(); void setupSMIMEProperties(); void refreshCertificate(); void changeExpiration(); void keysMayHaveChanged(); QIcon trustLevelIcon(const GpgME::UserID &uid) const; QString trustLevelText(const GpgME::UserID &uid) const; void showIssuerCertificate(); void updateKey(); void setUpdatedKey(const GpgME::Key &key); void keyListDone(const GpgME::KeyListResult &, const std::vector &, const QString &, const GpgME::Error &); void copyFingerprintToClipboard(); void setUpdateInProgress(bool updateInProgress); void setTabVisible(QWidget *tab, bool visible); private: CertificateDetailsWidget *const q; public: GpgME::Key key; bool updateInProgress = false; private: InfoField *attributeField(const QString &attributeName) { const auto keyValuePairIt = ui.smimeAttributeFields.find(attributeName); if (keyValuePairIt != ui.smimeAttributeFields.end()) { return (*keyValuePairIt).second.get(); } return nullptr; } private: struct UI { UserIdsWidget *userIDs = nullptr; std::map> smimeAttributeFields; std::unique_ptr smimeTrustLevelField; std::unique_ptr validFromField; std::unique_ptr expiresField; QAction *changeExpirationAction = nullptr; std::unique_ptr fingerprintField; QAction *copyFingerprintAction = nullptr; std::unique_ptr smimeIssuerField; QAction *showIssuerCertificateAction = nullptr; std::unique_ptr complianceField; std::unique_ptr trustedIntroducerField; std::unique_ptr primaryUserIdField; std::unique_ptr privateKeyInfoField; std::unique_ptr statusField; QListWidget *smimeAddressList = nullptr; QTabWidget *tabWidget = nullptr; SubKeysWidget *subKeysWidget = nullptr; WebOfTrustWidget *webOfTrustWidget = nullptr; TrustChainWidget *trustChainWidget = nullptr; CertificateDumpWidget *certificateDumpWidget = nullptr; CardInfoTab *cardInfoTab = nullptr; void setupUi(QWidget *parent) { auto mainLayout = new QVBoxLayout{parent}; { auto gridLayout = new QGridLayout; gridLayout->setColumnStretch(1, 1); int row = -1; row++; primaryUserIdField = std::make_unique(i18n("User ID:"), parent); gridLayout->addWidget(primaryUserIdField->label(), row, 0); gridLayout->addLayout(primaryUserIdField->layout(), row, 1); for (const auto &attribute : DN::attributeOrder()) { const auto attributeLabel = DN::attributeNameToLabel(attribute); if (attributeLabel.isEmpty()) { continue; } const auto labelWithColon = i18nc("interpunctation for labels", "%1:", attributeLabel); const auto &[it, inserted] = smimeAttributeFields.try_emplace(attribute, std::make_unique(labelWithColon, parent)); if (inserted) { row++; const auto &field = it->second; gridLayout->addWidget(field->label(), row, 0); gridLayout->addLayout(field->layout(), row, 1); } } row++; smimeTrustLevelField = std::make_unique(i18n("Trust level:"), parent); gridLayout->addWidget(smimeTrustLevelField->label(), row, 0); gridLayout->addLayout(smimeTrustLevelField->layout(), row, 1); row++; validFromField = std::make_unique(i18n("Valid from:"), parent); gridLayout->addWidget(validFromField->label(), row, 0); gridLayout->addLayout(validFromField->layout(), row, 1); row++; expiresField = std::make_unique(i18n("Valid until:"), parent); changeExpirationAction = new QAction{parent}; changeExpirationAction->setIcon(QIcon::fromTheme(QStringLiteral("editor"))); changeExpirationAction->setToolTip(i18nc("@info:tooltip", "Change the end of the validity period")); Kleo::setAccessibleName(changeExpirationAction, i18nc("@action:button", "Change Validity")); expiresField->setAction(changeExpirationAction); gridLayout->addWidget(expiresField->label(), row, 0); gridLayout->addLayout(expiresField->layout(), row, 1); row++; statusField = std::make_unique(i18n("Status:"), parent); gridLayout->addWidget(statusField->label(), row, 0); gridLayout->addLayout(statusField->layout(), row, 1); row++; fingerprintField = std::make_unique(i18n("Fingerprint:"), parent); if (QGuiApplication::clipboard()) { copyFingerprintAction = new QAction{parent}; copyFingerprintAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); copyFingerprintAction->setToolTip(i18nc("@info:tooltip", "Copy the fingerprint to the clipboard")); Kleo::setAccessibleName(copyFingerprintAction, i18nc("@action:button", "Copy fingerprint")); fingerprintField->setAction(copyFingerprintAction); } gridLayout->addWidget(fingerprintField->label(), row, 0); gridLayout->addLayout(fingerprintField->layout(), row, 1); row++; smimeIssuerField = std::make_unique(i18n("Issuer:"), parent); showIssuerCertificateAction = new QAction{parent}; showIssuerCertificateAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); showIssuerCertificateAction->setToolTip(i18nc("@info:tooltip", "Show the issuer certificate")); Kleo::setAccessibleName(showIssuerCertificateAction, i18nc("@action:button", "Show certificate")); smimeIssuerField->setAction(showIssuerCertificateAction); gridLayout->addWidget(smimeIssuerField->label(), row, 0); gridLayout->addLayout(smimeIssuerField->layout(), row, 1); row++; complianceField = std::make_unique(i18n("Compliance:"), parent); gridLayout->addWidget(complianceField->label(), row, 0); gridLayout->addLayout(complianceField->layout(), row, 1); row++; trustedIntroducerField = std::make_unique(i18n("Trusted introducer for:"), parent); gridLayout->addWidget(trustedIntroducerField->label(), row, 0); trustedIntroducerField->setToolTip(i18n("See certifications for details.")); gridLayout->addLayout(trustedIntroducerField->layout(), row, 1); row++; privateKeyInfoField = std::make_unique(i18n("Private Key:"), parent); gridLayout->addWidget(privateKeyInfoField->label(), row, 0); gridLayout->addLayout(privateKeyInfoField->layout(), row, 1); mainLayout->addLayout(gridLayout); } tabWidget = new QTabWidget(parent); + tabWidget->setDocumentMode(true); // we don't want a frame around the page widgets + tabWidget->tabBar()->setDrawBase(false); // only draw the tabs mainLayout->addWidget(tabWidget); userIDs = new UserIdsWidget(parent); tabWidget->addTab(userIDs, i18nc("@title:tab", "User IDs")); smimeAddressList = new QListWidget{parent}; + // Breeze draws no frame for scroll areas that are the only widget in a layout...unless we force it + smimeAddressList->setProperty("_breeze_force_frame", true); smimeAddressList->setAccessibleName(i18n("Related addresses")); smimeAddressList->setEditTriggers(QAbstractItemView::NoEditTriggers); smimeAddressList->setSelectionMode(QAbstractItemView::SingleSelection); tabWidget->addTab(smimeAddressList, i18nc("@title:tab", "Related Addresses")); subKeysWidget = new SubKeysWidget(parent); tabWidget->addTab(subKeysWidget, i18nc("@title:tab", "Subkeys")); webOfTrustWidget = new WebOfTrustWidget(parent); tabWidget->addTab(webOfTrustWidget, i18nc("@title:tab", "Certifications")); trustChainWidget = new TrustChainWidget(parent); tabWidget->addTab(trustChainWidget, i18nc("@title:tab", "Trust Chain Details")); cardInfoTab = new CardInfoTab(parent); tabWidget->addTab(cardInfoTab, i18nc("@title:tab", "Smartcard")); certificateDumpWidget = new CertificateDumpWidget(parent); tabWidget->addTab(certificateDumpWidget, i18nc("@title:tab", "Certificate Dump")); } } ui; }; CertificateDetailsWidget::Private::Private(CertificateDetailsWidget *qq) : q{qq} { ui.setupUi(q); connect(ui.changeExpirationAction, &QAction::triggered, q, [this]() { changeExpiration(); }); connect(ui.showIssuerCertificateAction, &QAction::triggered, q, [this]() { showIssuerCertificate(); }); if (ui.copyFingerprintAction) { connect(ui.copyFingerprintAction, &QAction::triggered, q, [this]() { copyFingerprintToClipboard(); }); } connect(Kleo::KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, q, [this]() { keysMayHaveChanged(); }); connect(ui.userIDs, &UserIdsWidget::updateKey, q, [this]() { updateKey(); }); } void CertificateDetailsWidget::Private::setupCommonProperties() { const bool isOpenPGP = key.protocol() == GpgME::OpenPGP; const bool isSMIME = key.protocol() == GpgME::CMS; const bool isOwnKey = key.hasSecret(); for (const auto &[_, field] : ui.smimeAttributeFields) { field->setVisible(isSMIME); } ui.smimeTrustLevelField->setVisible(isSMIME); // ui.validFromField->setVisible(true); // always visible // ui.expiresField->setVisible(true); // always visible if (isOpenPGP && isOwnKey) { ui.expiresField->setAction(ui.changeExpirationAction); } else { ui.expiresField->setAction(nullptr); } // ui.fingerprintField->setVisible(true); // always visible ui.smimeIssuerField->setVisible(isSMIME); ui.complianceField->setVisible(DeVSCompliance::isCompliant()); ui.trustedIntroducerField->setVisible(isOpenPGP); // may be hidden again by setupPGPProperties() // update availability of buttons ui.changeExpirationAction->setEnabled(canBeUsedForSecretKeyOperations(key)); // update values of protocol-independent UI elements ui.validFromField->setValue(Formatting::creationDateString(key), Formatting::accessibleCreationDate(key)); ui.expiresField->setValue(Formatting::expirationDateString(key, i18nc("Valid until:", "unlimited")), Formatting::accessibleExpirationDate(key)); ui.fingerprintField->setValue(Formatting::prettyID(key.primaryFingerprint()), Formatting::accessibleHexID(key.primaryFingerprint())); ui.statusField->setValue(Formatting::complianceStringShort(key)); QString storage; const auto &subkey = key.subkey(0); if (!key.hasSecret()) { storage = i18nc("not applicable", "n/a"); } else if (key.subkey(0).isCardKey()) { if (const char *serialNo = subkey.cardSerialNumber()) { storage = i18nc("As in 'this secret key is stored on smart card '", "smart card %1", QString::fromUtf8(serialNo)); } else { storage = i18nc("As in 'this secret key is stored on a smart card'", "smart card"); } } else if (!subkey.isSecret()) { storage = i18nc("key is 'offline key', i.e. secret key is not stored on this computer", "offline"); } else { storage = i18nc("As in 'this secret key is stored on this computer'", "on this computer"); } ui.privateKeyInfoField->setValue(storage); if (DeVSCompliance::isCompliant()) { ui.complianceField->setValue(Kleo::Formatting::complianceStringForKey(key)); } ui.cardInfoTab->setKey(key); } void CertificateDetailsWidget::Private::setUpSMIMEAdressList() { ui.smimeAddressList->clear(); const auto *const emailField = attributeField(QStringLiteral("EMAIL")); // add email address from primary user ID if not listed already as attribute field if (!emailField) { const auto ownerId = key.userID(0); const Kleo::DN dn(ownerId.id()); const QString dnEmail = dn[QStringLiteral("EMAIL")]; if (!dnEmail.isEmpty()) { ui.smimeAddressList->addItem(dnEmail); } } if (key.numUserIDs() > 1) { // iterate over the secondary user IDs #ifdef USE_RANGES for (const auto uids = key.userIDs(); const auto &uid : std::ranges::subrange(std::next(uids.begin()), uids.end())) { #else const auto uids = key.userIDs(); for (auto it = std::next(uids.begin()); it != uids.end(); ++it) { const auto &uid = *it; #endif const auto name = Kleo::Formatting::prettyName(uid); const auto email = Kleo::Formatting::prettyEMail(uid); QString itemText; if (name.isEmpty() && !email.isEmpty()) { // skip email addresses already listed in email attribute field if (emailField && email == emailField->value()) { continue; } itemText = email; } else { // S/MIME certificates sometimes contain urls where both // name and mail is empty. In that case we print whatever // the uid is as name. // // Can be ugly like (3:uri24:http://ca.intevation.org), but // this is better then showing an empty entry. itemText = QString::fromUtf8(uid.id()); } // avoid duplicate entries in the list if (ui.smimeAddressList->findItems(itemText, Qt::MatchExactly).empty()) { ui.smimeAddressList->addItem(itemText); } } } if (ui.smimeAddressList->count() == 0) { ui.tabWidget->setTabVisible(1, false); } } void CertificateDetailsWidget::Private::changeExpiration() { auto cmd = new Kleo::Commands::ChangeExpiryCommand(key); QObject::connect(cmd, &Kleo::Commands::ChangeExpiryCommand::finished, q, [this]() { ui.changeExpirationAction->setEnabled(true); }); ui.changeExpirationAction->setEnabled(false); cmd->start(); } 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); } } QIcon CertificateDetailsWidget::Private::trustLevelIcon(const GpgME::UserID &uid) const { if (updateInProgress) { return QIcon::fromTheme(QStringLiteral("emblem-question")); } switch (uid.validity()) { case GpgME::UserID::Unknown: case GpgME::UserID::Undefined: return QIcon::fromTheme(QStringLiteral("emblem-question")); case GpgME::UserID::Never: return QIcon::fromTheme(QStringLiteral("emblem-error")); case GpgME::UserID::Marginal: return QIcon::fromTheme(QStringLiteral("emblem-warning")); case GpgME::UserID::Full: case GpgME::UserID::Ultimate: return QIcon::fromTheme(QStringLiteral("emblem-success")); } return {}; } QString CertificateDetailsWidget::Private::trustLevelText(const GpgME::UserID &uid) const { return updateInProgress ? i18n("Updating...") : Formatting::validityShort(uid); } namespace { auto isGood(const GpgME::UserID::Signature &signature) { return signature.status() == GpgME::UserID::Signature::NoError // && !signature.isInvalid() // && 0x10 <= signature.certClass() && signature.certClass() <= 0x13; } 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::setTabVisible(QWidget *tab, bool visible) { ui.tabWidget->setTabVisible(ui.tabWidget->indexOf(tab), visible); } void CertificateDetailsWidget::Private::setupPGPProperties() { setTabVisible(ui.userIDs, true); setTabVisible(ui.smimeAddressList, false); setTabVisible(ui.subKeysWidget, true); setTabVisible(ui.webOfTrustWidget, true); setTabVisible(ui.trustChainWidget, false); setTabVisible(ui.certificateDumpWidget, false); ui.userIDs->setKey(key); ui.subKeysWidget->setKey(key); ui.webOfTrustWidget->setKey(key); const auto trustDomains = accumulateTrustDomains(key.userIDs()); ui.trustedIntroducerField->setVisible(!trustDomains.empty()); ui.trustedIntroducerField->setValue(QStringList(std::begin(trustDomains), std::end(trustDomains)).join(u", ")); ui.primaryUserIdField->setValue(Formatting::prettyUserID(key.userID(0))); ui.primaryUserIdField->setVisible(true); } 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() { setTabVisible(ui.userIDs, false); setTabVisible(ui.smimeAddressList, true); setTabVisible(ui.subKeysWidget, false); setTabVisible(ui.webOfTrustWidget, false); setTabVisible(ui.trustChainWidget, true); setTabVisible(ui.certificateDumpWidget, true); ui.trustChainWidget->setKey(key); const auto ownerId = key.userID(0); const Kleo::DN dn(ownerId.id()); for (const auto &[attributeName, field] : ui.smimeAttributeFields) { const QString attributeValue = dn[attributeName]; field->setValue(attributeValue); field->setVisible(!attributeValue.isEmpty()); } ui.smimeTrustLevelField->setIcon(trustLevelIcon(ownerId)); ui.smimeTrustLevelField->setValue(trustLevelText(ownerId)); const Kleo::DN issuerDN(key.issuerName()); const QString issuerCN = issuerDN[QStringLiteral("CN")]; const QString issuer = issuerCN.isEmpty() ? QString::fromUtf8(key.issuerName()) : issuerCN; ui.smimeIssuerField->setValue(issuer); ui.smimeIssuerField->setToolTip(formatDNToolTip(issuerDN)); ui.showIssuerCertificateAction->setEnabled(!key.isRoot()); ui.primaryUserIdField->setVisible(false); ui.certificateDumpWidget->setKey(key); setUpSMIMEAdressList(); } void CertificateDetailsWidget::Private::showIssuerCertificate() { // there is either one or no parent key const auto parentKeys = KeyCache::instance()->findIssuers(key, KeyCache::NoOption); if (parentKeys.empty()) { KMessageBox::error(q, i18n("The issuer certificate could not be found locally.")); return; } auto cmd = new Kleo::Commands::DetailsCommand(parentKeys.front()); cmd->setParentWidget(q); cmd->start(); } void CertificateDetailsWidget::Private::copyFingerprintToClipboard() { if (auto clipboard = QGuiApplication::clipboard()) { clipboard->setText(QString::fromLatin1(key.primaryFingerprint())); } } CertificateDetailsWidget::CertificateDetailsWidget(QWidget *parent) : QWidget{parent} , d{std::make_unique(this)} { } CertificateDetailsWidget::~CertificateDetailsWidget() = default; void CertificateDetailsWidget::Private::keyListDone(const GpgME::KeyListResult &, const std::vector &keys, const QString &, const GpgME::Error &) { setUpdateInProgress(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->setUpdateInProgress(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; } void CertificateDetailsWidget::Private::setUpdateInProgress(bool updateInProgress) { this->updateInProgress = updateInProgress; ui.userIDs->setUpdateInProgress(updateInProgress); } #include "moc_certificatedetailswidget.cpp" diff --git a/src/dialogs/certificatedumpwidget.cpp b/src/dialogs/certificatedumpwidget.cpp index f22b939a9..fe23b0f72 100644 --- a/src/dialogs/certificatedumpwidget.cpp +++ b/src/dialogs/certificatedumpwidget.cpp @@ -1,77 +1,78 @@ // SPDX-FileCopyrightText: 2024 g10 Code GmbH // SPDX-FileContributor: Tobias Fella // SPDX-License-Identifier: GPL-2.0-or-later #include #include "certificatedumpwidget.h" #include "commands/dumpcertificatecommand.h" #include #include #include #include #include #include #include class CertificateDumpWidget::Private { CertificateDumpWidget *const q; public: Private(CertificateDumpWidget *qq) : q{qq} , ui{qq} { } GpgME::Key key; struct UI { QVBoxLayout *mainLayout; QTextEdit *textEdit; UI(QWidget *widget) { mainLayout = new QVBoxLayout{widget}; mainLayout->setContentsMargins({}); - mainLayout->setSpacing(0); textEdit = new QTextEdit(widget); + // Breeze draws no frame for scroll areas that are the only widget in a layout...unless we force it + textEdit->setProperty("_breeze_force_frame", true); textEdit->setReadOnly(true); textEdit->setFontFamily(QStringLiteral("monospace")); mainLayout->addWidget(textEdit); } } ui; }; CertificateDumpWidget::CertificateDumpWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { } CertificateDumpWidget::~CertificateDumpWidget() = default; void CertificateDumpWidget::setKey(const GpgME::Key &key) { d->key = key; auto command = new Kleo::Commands::DumpCertificateCommand(key); command->setUseDialog(false); connect(command, &Kleo::Command::finished, this, [command, this]() { d->ui.textEdit->setText(command->output().join(u'\n')); }); command->start(); } GpgME::Key CertificateDumpWidget::key() const { return d->key; } #include "moc_certificatedumpwidget.cpp" diff --git a/src/dialogs/subkeyswidget.cpp b/src/dialogs/subkeyswidget.cpp index 10e072417..7da2d57ba 100644 --- a/src/dialogs/subkeyswidget.cpp +++ b/src/dialogs/subkeyswidget.cpp @@ -1,425 +1,420 @@ /* dialogs/subkeyswidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "subkeyswidget.h" #include "commands/addsubkeycommand.h" #include "commands/changeexpirycommand.h" #include "commands/exportsecretsubkeycommand.h" #include "commands/importpaperkeycommand.h" #include "commands/keytocardcommand.h" #include "exportdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(GpgME::Subkey) using namespace Kleo; using namespace Kleo::Commands; static QPushButton *addActionButton(QLayout *buttonBox, QAction *action, bool bindVisibility = true) { if (!action) { return nullptr; } 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, bindVisibility]() { button->setEnabled(action->isEnabled()); if (bindVisibility) { button->setVisible(action->isVisible()); } }); QObject::connect(button, &QPushButton::clicked, action, &QAction::trigger); return button; } class SubKeysWidget::Private { SubKeysWidget *const q; public: Private(SubKeysWidget *qq) : q{qq} , ui{qq} { ui.subkeysTree->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui.subkeysTree, &QAbstractItemView::customContextMenuRequested, q, [this](const QPoint &p) { tableContextMenuRequested(p); }); connect(Kleo::KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, q, [this]() { keysMayHaveChanged(); }); connect(ui.moreButton, &QPushButton::clicked, q, [this]() { auto menu = new QMenu(q); menu->addAction(ui.exportOpenSSHAction); menu->addAction(ui.transferToSmartcardAction); menu->addAction(ui.exportSecretAction); menu->addAction(ui.restoreAction); menu->popup(ui.moreButton->mapToGlobal(QPoint())); }); } void changeValidity(const GpgME::Subkey &subkey); void exportSSH(const GpgME::Subkey &subkey); void keyToCard(const GpgME::Subkey &subkey); void exportSecret(const GpgME::Subkey &subkey); void importPaperKey(); void addSubkey(); void updateState(); private: void tableContextMenuRequested(const QPoint &p); void keysMayHaveChanged(); public: GpgME::Key key; public: struct UI { QVBoxLayout *mainLayout; TreeWidget *subkeysTree; QAction *changeValidityAction = nullptr; QAction *transferToSmartcardAction = nullptr; QAction *exportSecretAction = nullptr; QAction *addSubkeyAction = nullptr; QAction *restoreAction = nullptr; QPushButton *restoreBtn = nullptr; QAction *exportOpenSSHAction = nullptr; QPushButton *exportOpenSSHBtn = nullptr; QPushButton *moreButton = nullptr; UI(QWidget *widget) { mainLayout = new QVBoxLayout{widget}; mainLayout->setContentsMargins({}); - mainLayout->setSpacing(0); subkeysTree = new TreeWidget{widget}; subkeysTree->setAccessibleName(i18nc("@label", "Subkeys")); subkeysTree->setRootIsDecorated(false); subkeysTree->setHeaderLabels({ i18nc("@title:column", "ID"), i18nc("@title:column", "Fingerprint"), i18nc("@title:column", "Valid From"), i18nc("@title:column", "Valid Until"), i18nc("@title:column", "Status"), i18nc("@title:column", "Algorithm"), i18nc("@title:column", "Usage"), i18nc("@title:column", "Storage"), }); mainLayout->addWidget(subkeysTree); - auto separator = new KSeparator(widget); - mainLayout->addWidget(separator); - { auto buttonRow = new QHBoxLayout; - buttonRow->setSpacing(widget->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing)); addSubkeyAction = new QAction({}, i18nc("@action:button", "Add subkey")); changeValidityAction = new QAction({}, i18nc("@action:button", "Change validity"), widget); exportOpenSSHAction = new QAction({}, i18nc("@action:button", "Export OpenSSH key"), widget); restoreAction = new QAction({}, i18nc("@action:button", "Restore printed backup"), widget); transferToSmartcardAction = new QAction({}, i18nc("@action:button", "Transfer to smartcard"), widget); exportSecretAction = new QAction({}, i18nc("@action:button", "Export secret subkey"), widget); addActionButton(buttonRow, addSubkeyAction); addActionButton(buttonRow, changeValidityAction); exportOpenSSHBtn = addActionButton(buttonRow, exportOpenSSHAction, false); restoreBtn = addActionButton(buttonRow, restoreAction, false); moreButton = new QPushButton(QIcon::fromTheme(QStringLiteral("application-menu")), {}); moreButton->setToolTip(i18nc("@info:tooltip", "Show more options")); buttonRow->addWidget(moreButton); buttonRow->addStretch(1); mainLayout->addLayout(buttonRow); } } } ui; }; void SubKeysWidget::Private::changeValidity(const GpgME::Subkey &subkey) { ui.changeValidityAction->setEnabled(false); auto cmd = new ChangeExpiryCommand(subkey.parent()); cmd->setSubkey(subkey); ui.subkeysTree->setEnabled(false); connect(cmd, &ChangeExpiryCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); key.update(); q->setKey(key); ui.changeValidityAction->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); } void SubKeysWidget::Private::exportSSH(const GpgME::Subkey &subkey) { QScopedPointer dlg(new ExportDialog(q)); dlg->setKey(subkey, static_cast(GpgME::Context::ExportSSH)); dlg->exec(); } void SubKeysWidget::Private::importPaperKey() { ui.restoreAction->setEnabled(false); auto cmd = new ImportPaperKeyCommand(key); ui.subkeysTree->setEnabled(false); connect(cmd, &ImportPaperKeyCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); ui.restoreAction->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); } void SubKeysWidget::Private::keyToCard(const GpgME::Subkey &subkey) { auto cmd = new KeyToCardCommand(subkey); ui.subkeysTree->setEnabled(false); connect(cmd, &KeyToCardCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); } void SubKeysWidget::Private::exportSecret(const GpgME::Subkey &subkey) { ui.exportSecretAction->setEnabled(false); auto cmd = new ExportSecretSubkeyCommand{{subkey}}; ui.subkeysTree->setEnabled(false); connect(cmd, &ExportSecretSubkeyCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); ui.exportSecretAction->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); } void SubKeysWidget::Private::addSubkey() { ui.addSubkeyAction->setEnabled(false); const auto cmd = new AddSubkeyCommand(q->key()); connect(cmd, &AddSubkeyCommand::finished, q, [this]() { q->key().update(); ui.addSubkeyAction->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); } void SubKeysWidget::Private::tableContextMenuRequested(const QPoint &p) { auto item = ui.subkeysTree->itemAt(p); if (!item) { return; } const auto subkey = item->data(0, Qt::UserRole).value(); const bool isOwnKey = subkey.parent().hasSecret(); const bool secretSubkeyStoredInKeyRing = subkey.isSecret() && !subkey.isCardKey(); auto menu = new QMenu(q); connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); if (isOwnKey) { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("change-date-symbolic")), i18n("Change validity"), q, [this, subkey]() { changeValidity(subkey); }); action->setEnabled(canBeUsedForSecretKeyOperations(subkey.parent())); } if (subkey.canAuthenticate()) { menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")), i18n("Export OpenSSH key"), q, [this, subkey]() { exportSSH(subkey); }); } if (isOwnKey) { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("send-to-symbolic")), i18n("Transfer to smartcard"), q, [this, subkey]() { keyToCard(subkey); }); action->setEnabled(secretSubkeyStoredInKeyRing && !KeyToCardCommand::getSuitableCards(subkey).empty()); } const bool isPrimarySubkey = subkey.keyID() == key.keyID(); if (isOwnKey && !isPrimarySubkey) { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")), i18n("Export secret subkey"), q, [this, subkey]() { exportSecret(subkey); }); action->setEnabled(secretSubkeyStoredInKeyRing); } menu->popup(ui.subkeysTree->viewport()->mapToGlobal(p)); } void SubKeysWidget::Private::keysMayHaveChanged() { qCDebug(KLEOPATRA_LOG) << q << __func__; const auto updatedKey = Kleo::KeyCache::instance()->findByFingerprint(key.primaryFingerprint()); if (!updatedKey.isNull()) { q->setKey(updatedKey); } } void SubKeysWidget::Private::updateState() { const auto currentIndex = ui.subkeysTree->currentIndex().row(); const auto &subkey = key.subkey(currentIndex); const bool secretSubkeyStoredInKeyRing = subkey.isSecret() && !subkey.isCardKey(); ui.exportOpenSSHAction->setEnabled(subkey.canAuthenticate()); ui.changeValidityAction->setEnabled(key.hasSecret() && canBeUsedForSecretKeyOperations(subkey.parent())); ui.exportSecretAction->setEnabled(key.hasSecret() && subkey.fingerprint() != key.primaryFingerprint() && secretSubkeyStoredInKeyRing); ui.restoreAction->setEnabled(!secretSubkeyStoredInKeyRing); ui.transferToSmartcardAction->setEnabled(secretSubkeyStoredInKeyRing && !KeyToCardCommand::getSuitableCards(subkey).empty()); } SubKeysWidget::SubKeysWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { connect(d->ui.subkeysTree, &TreeWidget::currentItemChanged, this, [this] { d->updateState(); }); connect(d->ui.changeValidityAction, &QAction::triggered, this, [this] { d->changeValidity(d->key.subkey(d->ui.subkeysTree->currentIndex().row())); }); connect(d->ui.exportOpenSSHAction, &QAction::triggered, this, [this] { d->exportSSH(d->key.subkey(d->ui.subkeysTree->currentIndex().row())); }); connect(d->ui.restoreAction, &QAction::triggered, this, [this] { d->importPaperKey(); }); connect(d->ui.transferToSmartcardAction, &QAction::triggered, this, [this] { d->keyToCard(d->key.subkey(d->ui.subkeysTree->currentIndex().row())); }); connect(d->ui.exportSecretAction, &QAction::triggered, this, [this] { d->exportSecret(d->key.subkey(d->ui.subkeysTree->currentIndex().row())); }); connect(d->ui.addSubkeyAction, &QAction::triggered, this, [this]() { d->addSubkey(); }); } SubKeysWidget::~SubKeysWidget() = default; void SubKeysWidget::setKey(const GpgME::Key &key) { if (key.protocol() != GpgME::OpenPGP) { return; } d->key = key; const auto currentItem = d->ui.subkeysTree->currentItem(); const QByteArray selectedKeyFingerprint = currentItem ? QByteArray(currentItem->data(0, Qt::UserRole).value().fingerprint()) : QByteArray(); d->ui.subkeysTree->clear(); const auto subkeys = key.subkeys(); for (const auto &subkey : subkeys) { auto item = new QTreeWidgetItem; item->setData(0, Qt::DisplayRole, Formatting::prettyID(subkey.keyID())); item->setData(0, Qt::AccessibleTextRole, Formatting::accessibleHexID(subkey.keyID())); item->setData(0, Qt::UserRole, QVariant::fromValue(subkey)); item->setData(1, Qt::DisplayRole, Formatting::prettyID(subkey.fingerprint())); item->setData(1, Qt::AccessibleTextRole, Formatting::accessibleHexID(subkey.fingerprint())); item->setData(2, Qt::DisplayRole, Kleo::Formatting::creationDateString(subkey)); item->setData(2, Qt::AccessibleTextRole, Formatting::accessibleCreationDate(subkey)); item->setData(3, Qt::DisplayRole, subkey.neverExpires() ? Kleo::Formatting::expirationDateString(subkey.parent()) : Kleo::Formatting::expirationDateString(subkey)); item->setData(3, Qt::AccessibleTextRole, subkey.neverExpires() ? Kleo::Formatting::accessibleExpirationDate(subkey.parent()) : Kleo::Formatting::accessibleExpirationDate(subkey)); item->setData(4, Qt::DisplayRole, Kleo::Formatting::validityShort(subkey)); item->setData(5, Qt::DisplayRole, Kleo::Formatting::prettyAlgorithmName(subkey.algoName())); item->setData(6, Qt::DisplayRole, Kleo::Formatting::usageString(subkey)); const auto isPrimary = subkey.keyID() == key.keyID(); if (!key.hasSecret()) { item->setData(7, Qt::DisplayRole, i18nc("not applicable", "n/a")); } else if (subkey.isCardKey()) { if (const char *serialNo = subkey.cardSerialNumber()) { item->setData(7, Qt::DisplayRole, i18nc("smart card ", "smart card %1", QString::fromUtf8(serialNo))); } else { item->setData(7, Qt::DisplayRole, i18n("smart card")); } } else if (isPrimary && key.hasSecret() && !subkey.isSecret()) { item->setData(7, Qt::DisplayRole, i18nc("key is 'offline key', i.e. secret key is not stored on this computer", "offline")); } else if (subkey.isSecret()) { item->setData(7, Qt::DisplayRole, i18n("on this computer")); } else { item->setData(7, Qt::DisplayRole, i18nc("unknown storage location", "unknown")); } d->ui.subkeysTree->addTopLevelItem(item); if (subkey.fingerprint() == selectedKeyFingerprint) { d->ui.subkeysTree->setCurrentItem(item); } } d->ui.subkeysTree->header()->resizeSections(QHeaderView::ResizeToContents); d->ui.changeValidityAction->setVisible(key.hasSecret()); d->ui.exportSecretAction->setVisible(key.hasSecret()); d->ui.transferToSmartcardAction->setVisible(key.hasSecret()); d->ui.addSubkeyAction->setVisible(key.hasSecret()); d->ui.restoreAction->setVisible(true); d->ui.exportOpenSSHAction->setEnabled(false); d->ui.exportOpenSSHBtn->setVisible(!key.hasSecret()); d->ui.exportOpenSSHBtn->setEnabled(false); d->ui.restoreBtn->setVisible(!key.hasSecret()); d->ui.moreButton->setVisible(key.hasSecret()); d->updateState(); if (!d->ui.subkeysTree->restoreColumnLayout(QStringLiteral("SubkeysWidget"))) { d->ui.subkeysTree->hideColumn(1); } for (int i = 0; i < d->ui.subkeysTree->columnCount(); i++) { d->ui.subkeysTree->resizeColumnToContents(i); } } GpgME::Key SubKeysWidget::key() const { return d->key; } diff --git a/src/dialogs/trustchainwidget.cpp b/src/dialogs/trustchainwidget.cpp index 5b1633995..2a47ff2e4 100644 --- a/src/dialogs/trustchainwidget.cpp +++ b/src/dialogs/trustchainwidget.cpp @@ -1,78 +1,94 @@ /* SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include "trustchainwidget.h" -#include "ui_trustchainwidget.h" #include "kleopatra_debug.h" +#include + #include #include #include #include +#include #include #include #include class TrustChainWidget::Private { + TrustChainWidget *const q; + public: Private(TrustChainWidget *qq) : q(qq) + , ui{qq} { - Q_UNUSED(q); } GpgME::Key key; - Ui::TrustChainWidget ui; -private: - TrustChainWidget *const q; + struct UI { + QTreeWidget *treeWidget; + + UI(QWidget *widget) + { + auto mainLayout = new QVBoxLayout{widget}; + mainLayout->setContentsMargins({}); + + treeWidget = new QTreeWidget{widget}; + // Breeze draws no frame for scroll areas that are the only widget in a layout...unless we force it + treeWidget->setProperty("_breeze_force_frame", true); + treeWidget->setHeaderHidden(true); + + mainLayout->addWidget(treeWidget); + } + } ui; }; TrustChainWidget::TrustChainWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { - d->ui.setupUi(this); } TrustChainWidget::~TrustChainWidget() { } void TrustChainWidget::setKey(const GpgME::Key &key) { if (key.protocol() != GpgME::CMS) { qCDebug(KLEOPATRA_LOG) << "Trust chain is only supported for CMS keys"; return; } d->key = key; d->ui.treeWidget->clear(); const auto chain = Kleo::KeyCache::instance()->findIssuers(key, Kleo::KeyCache::RecursiveSearch | Kleo::KeyCache::IncludeSubject); if (chain.empty()) { return; } QTreeWidgetItem *last = nullptr; if (!chain.back().isRoot()) { last = new QTreeWidgetItem(d->ui.treeWidget); last->setText(0, i18n("Issuer Certificate Not Found (%1)", Kleo::DN(chain.back().issuerName()).prettyDN())); const QBrush &fg = d->ui.treeWidget->palette().brush(QPalette::Disabled, QPalette::WindowText); last->setForeground(0, fg); } for (auto it = chain.rbegin(), end = chain.rend(); it != end; ++it) { last = last ? new QTreeWidgetItem(last) : new QTreeWidgetItem(d->ui.treeWidget); last->setText(0, Kleo::DN(it->userID(0).id()).prettyDN()); } d->ui.treeWidget->expandAll(); } GpgME::Key TrustChainWidget::key() const { return d->key; } diff --git a/src/dialogs/trustchainwidget.ui b/src/dialogs/trustchainwidget.ui deleted file mode 100644 index dd1d4fa90..000000000 --- a/src/dialogs/trustchainwidget.ui +++ /dev/null @@ -1,45 +0,0 @@ - - - TrustChainWidget - - - - 0 - 0 - 650 - 330 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - false - - - - 1 - - - - - - - - - diff --git a/src/dialogs/useridswidget.cpp b/src/dialogs/useridswidget.cpp index e4e615ae8..65100c8a2 100644 --- a/src/dialogs/useridswidget.cpp +++ b/src/dialogs/useridswidget.cpp @@ -1,533 +1,528 @@ // SPDX-FileCopyrightText: 2024 g10 Code GmbH // SPDX-FileContributor: Tobias Fella // SPDX-License-Identifier: GPL-2.0-or-later #include "useridswidget.h" #include "commands/adduseridcommand.h" #include "commands/certifycertificatecommand.h" #include "commands/revokecertificationcommand.h" #include "commands/revokeuseridcommand.h" #include "commands/setprimaryuseridcommand.h" #ifdef MAILAKONADI_ENABLED #include "commands/exportopenpgpcerttoprovidercommand.h" #endif // MAILAKONADI_ENABLED #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 "kleopatra_debug.h" using namespace Kleo; Q_DECLARE_METATYPE(GpgME::UserID) namespace { std::vector selectedUserIDs(const QTreeWidget *treeWidget) { if (!treeWidget) { return {}; } std::vector userIDs; const auto selected = treeWidget->selectedItems(); std::transform(selected.begin(), selected.end(), std::back_inserter(userIDs), [](const QTreeWidgetItem *item) { return item->data(0, Qt::UserRole).value(); }); return userIDs; } static QPushButton *addActionButton(QLayout *buttonBox, QAction *action) { if (!action) { return nullptr; } 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); return button; } } class UserIdsWidget::Private { public: Private(UserIdsWidget *qq) : q{qq} { } TreeWidget *userIDTable = nullptr; QPushButton *addUserIDBtn = nullptr; QPushButton *revokeUserIDBtn = nullptr; QPushButton *certifyBtn = nullptr; QPushButton *revokeCertificationsBtn = nullptr; QAction *setPrimaryUserIDAction = nullptr; QAction *certifyAction = nullptr; QAction *revokeCertificationsAction = nullptr; GpgME::Key key; bool updateInProgress = false; QPushButton *moreButton = nullptr; QHBoxLayout *buttonRow = nullptr; QString trustLevelText(const GpgME::UserID &uid) const; QIcon trustLevelIcon(const GpgME::UserID &uid) const; QString tofuTooltipString(const GpgME::UserID &uid) const; void setUpUserIDTable(); void addUserID(); void updateUserIDActions(); void setPrimaryUserID(const GpgME::UserID &uid = {}); void certifyUserIDs(); void revokeCertifications(); void revokeUserID(const GpgME::UserID &uid); void revokeSelectedUserID(); void userIDTableContextMenuRequested(const QPoint &p); private: UserIdsWidget *const q; }; UserIdsWidget::UserIdsWidget(QWidget *parent) : QWidget{parent} , d{std::make_unique(this)} { auto userIDsLayout = new QVBoxLayout{this}; userIDsLayout->setContentsMargins({}); - userIDsLayout->setSpacing(0); d->userIDTable = new TreeWidget{parent}; d->userIDTable->setAccessibleName(i18n("User IDs")); QTreeWidgetItem *__qtreewidgetitem = new QTreeWidgetItem(); __qtreewidgetitem->setText(0, QString::fromUtf8("1")); d->userIDTable->setHeaderItem(__qtreewidgetitem); d->userIDTable->setEditTriggers(QAbstractItemView::NoEditTriggers); d->userIDTable->setSelectionMode(QAbstractItemView::ExtendedSelection); d->userIDTable->setRootIsDecorated(false); d->userIDTable->setUniformRowHeights(true); d->userIDTable->setAllColumnsShowFocus(false); userIDsLayout->addWidget(d->userIDTable); - auto separator = new KSeparator(parent); - userIDsLayout->addWidget(separator); - d->buttonRow = new QHBoxLayout; - d->buttonRow->setSpacing(parent->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing)); d->addUserIDBtn = new QPushButton(i18nc("@action:button", "Add User ID"), parent); d->buttonRow->addWidget(d->addUserIDBtn); d->revokeUserIDBtn = new QPushButton(i18nc("@action:button", "Revoke User ID"), parent); d->buttonRow->addWidget(d->revokeUserIDBtn); d->setPrimaryUserIDAction = new QAction({}, i18nc("@action:button", "Flag as Primary")); d->setPrimaryUserIDAction->setToolTip(i18nc("@info:tooltip", "Flag the selected user ID as the primary user ID of this key.")); d->certifyAction = new QAction({}, i18nc("@action:button", "Certify User IDs")); d->revokeCertificationsAction = new QAction({}, i18nc("@action:button", "Revoke Certifications")); d->certifyBtn = addActionButton(d->buttonRow, d->certifyAction); d->revokeCertificationsBtn = addActionButton(d->buttonRow, d->revokeCertificationsAction); d->moreButton = new QPushButton(QIcon::fromTheme(QStringLiteral("application-menu")), {}); d->moreButton->setToolTip(i18nc("@info:tooltip", "Show more options")); d->buttonRow->addWidget(d->moreButton); connect(d->moreButton, &QPushButton::clicked, this, [this]() { auto menu = new QMenu(this); menu->addAction(d->setPrimaryUserIDAction); menu->addAction(d->certifyAction); menu->addAction(d->revokeCertificationsAction); menu->popup(d->moreButton->mapToGlobal(QPoint())); }); d->buttonRow->addStretch(1); userIDsLayout->addLayout(d->buttonRow); setLayout(userIDsLayout); connect(d->addUserIDBtn, &QPushButton::clicked, this, [this]() { d->addUserID(); }); connect(d->userIDTable, &QTreeWidget::itemSelectionChanged, this, [this]() { d->updateUserIDActions(); }); connect(d->setPrimaryUserIDAction, &QAction::triggered, this, [this]() { d->setPrimaryUserID(); }); connect(d->certifyAction, &QAction::triggered, this, [this]() { d->certifyUserIDs(); }); connect(d->revokeCertificationsAction, &QAction::triggered, this, [this]() { d->revokeCertifications(); }); connect(d->revokeUserIDBtn, &QPushButton::clicked, this, [this]() { d->revokeSelectedUserID(); }); d->userIDTable->setContextMenuPolicy(Qt::CustomContextMenu); connect(d->userIDTable, &QAbstractItemView::customContextMenuRequested, this, [this](const QPoint &p) { d->userIDTableContextMenuRequested(p); }); } void UserIdsWidget::Private::updateUserIDActions() { const auto userIDs = selectedUserIDs(userIDTable); const auto singleUserID = userIDs.size() == 1 ? userIDs.front() : GpgME::UserID{}; const bool isPrimaryUserID = !singleUserID.isNull() && (userIDTable->selectedItems().front() == userIDTable->topLevelItem(0)); setPrimaryUserIDAction->setEnabled(!singleUserID.isNull() // && !isPrimaryUserID // && !Kleo::isRevokedOrExpired(singleUserID) // && canBeUsedForSecretKeyOperations(key)); revokeUserIDBtn->setEnabled(!singleUserID.isNull() && canCreateCertifications(key) && canRevokeUserID(singleUserID)); } UserIdsWidget::~UserIdsWidget() = default; GpgME::Key UserIdsWidget::key() const { return d->key; } void UserIdsWidget::setKey(const GpgME::Key &key) { d->key = key; d->setUpUserIDTable(); const bool isOwnKey = key.hasSecret(); const auto isLocalKey = !isRemoteKey(key); const auto keyCanBeCertified = Kleo::canBeCertified(key); const auto userCanSignUserIDs = userHasCertificationKey(); d->addUserIDBtn->setVisible(isOwnKey); d->addUserIDBtn->setEnabled(canBeUsedForSecretKeyOperations(key)); d->setPrimaryUserIDAction->setVisible(isOwnKey); d->setPrimaryUserIDAction->setEnabled(false); // requires a selected user ID d->certifyAction->setVisible(true); // always visible (for OpenPGP keys) d->certifyBtn->setVisible(!isOwnKey); d->revokeCertificationsBtn->setVisible(!isOwnKey); d->moreButton->setVisible(isOwnKey); d->certifyAction->setEnabled(isLocalKey && keyCanBeCertified && userCanSignUserIDs); d->revokeCertificationsAction->setVisible(Kleo::Commands::RevokeCertificationCommand::isSupported()); d->revokeCertificationsAction->setEnabled(userCanSignUserIDs && isLocalKey); d->revokeUserIDBtn->setVisible(isOwnKey); d->revokeUserIDBtn->setEnabled(false); // requires a selected user ID } void UserIdsWidget::Private::setUpUserIDTable() { userIDTable->clear(); QStringList headers = {i18n("Email"), i18n("Name"), i18n("Trust Level"), i18n("Tags"), i18n("Origin")}; userIDTable->setColumnCount(headers.count()); userIDTable->setColumnWidth(0, 200); userIDTable->setColumnWidth(1, 200); userIDTable->setHeaderLabels(headers); const auto uids = key.userIDs(); for (unsigned int i = 0; i < uids.size(); ++i) { const auto &uid = uids[i]; auto item = new QTreeWidgetItem; const QString toolTip = tofuTooltipString(uid); item->setData(0, Qt::UserRole, QVariant::fromValue(uid)); auto pMail = Kleo::Formatting::prettyEMail(uid); auto pName = Kleo::Formatting::prettyName(uid); item->setData(0, Qt::DisplayRole, pMail); item->setData(0, Qt::ToolTipRole, toolTip); item->setData(0, Qt::AccessibleTextRole, pMail.isEmpty() ? i18nc("text for screen readers for an empty email address", "no email") : pMail); item->setData(1, Qt::DisplayRole, pName); item->setData(1, Qt::ToolTipRole, toolTip); item->setData(2, Qt::DecorationRole, trustLevelIcon(uid)); item->setData(2, Qt::DisplayRole, trustLevelText(uid)); item->setData(2, Qt::ToolTipRole, toolTip); GpgME::Error err; QStringList tagList; for (const auto &tag : uid.remarks(Tags::tagKeys(), err)) { if (err) { qCWarning(KLEOPATRA_LOG) << "Getting remarks for user ID" << uid.id() << "failed:" << err; } tagList << QString::fromStdString(tag); } qCDebug(KLEOPATRA_LOG) << "tagList:" << tagList; const auto tags = tagList.join(QStringLiteral("; ")); item->setData(3, Qt::DisplayRole, tags); item->setData(3, Qt::ToolTipRole, toolTip); item->setData(4, Qt::DisplayRole, Formatting::origin(uid.origin())); userIDTable->addTopLevelItem(item); } userIDTable->restoreColumnLayout(QStringLiteral("UserIDTable")); if (!Tags::tagsEnabled()) { userIDTable->hideColumn(3); } for (int i = 0; i < userIDTable->columnCount(); i++) { userIDTable->resizeColumnToContents(i); } } QString UserIdsWidget::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; } QIcon UserIdsWidget::Private::trustLevelIcon(const GpgME::UserID &uid) const { if (updateInProgress) { return QIcon::fromTheme(QStringLiteral("emblem-question")); } switch (uid.validity()) { case GpgME::UserID::Unknown: case GpgME::UserID::Undefined: return QIcon::fromTheme(QStringLiteral("emblem-question")); case GpgME::UserID::Never: return QIcon::fromTheme(QStringLiteral("emblem-error")); case GpgME::UserID::Marginal: return QIcon::fromTheme(QStringLiteral("emblem-warning")); case GpgME::UserID::Full: case GpgME::UserID::Ultimate: return QIcon::fromTheme(QStringLiteral("emblem-success")); } return {}; } QString UserIdsWidget::Private::trustLevelText(const GpgME::UserID &uid) const { return updateInProgress ? i18n("Updating...") : Formatting::validityShort(uid); } void UserIdsWidget::Private::addUserID() { auto cmd = new Kleo::Commands::AddUserIDCommand(key); QObject::connect(cmd, &Kleo::Commands::AddUserIDCommand::finished, q, [this]() { addUserIDBtn->setEnabled(true); Q_EMIT q->updateKey(); }); addUserIDBtn->setEnabled(false); cmd->start(); } void UserIdsWidget::Private::setPrimaryUserID(const GpgME::UserID &uid) { auto userId = uid; if (userId.isNull()) { const auto userIDs = selectedUserIDs(userIDTable); if (userIDs.size() != 1) { return; } userId = userIDs.front(); } auto cmd = new Kleo::Commands::SetPrimaryUserIDCommand(userId); connect(cmd, &Kleo::Commands::SetPrimaryUserIDCommand::finished, q, [this]() { userIDTable->setEnabled(true); // the Flag As Primary button will be updated by the key update Q_EMIT q->updateKey(); }); userIDTable->setEnabled(false); setPrimaryUserIDAction->setEnabled(false); cmd->start(); } void UserIdsWidget::Private::certifyUserIDs() { const auto userIDs = selectedUserIDs(userIDTable); auto cmd = userIDs.empty() ? new Kleo::Commands::CertifyCertificateCommand{key} // : new Kleo::Commands::CertifyCertificateCommand{userIDs}; connect(cmd, &Kleo::Commands::CertifyCertificateCommand::finished, q, [this]() { Q_EMIT q->updateKey(); certifyAction->setEnabled(true); }); certifyAction->setEnabled(false); cmd->start(); } void UserIdsWidget::Private::revokeCertifications() { const auto userIDs = selectedUserIDs(userIDTable); auto cmd = userIDs.empty() ? new Kleo::Commands::RevokeCertificationCommand{key} // : new Kleo::Commands::RevokeCertificationCommand{userIDs}; connect(cmd, &Kleo::Command::finished, q, [this]() { Q_EMIT q->updateKey(); revokeCertificationsAction->setEnabled(true); }); revokeCertificationsAction->setEnabled(false); cmd->start(); } void UserIdsWidget::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::ok(); confirmButton.setText(i18nc("@action:button", "Revoke User ID")); confirmButton.setToolTip({}); const auto choice = KMessageBox::questionTwoActions(q->window(), message, i18nc("@title:window", "Confirm Revocation"), confirmButton, KStandardGuiItem::cancel(), {}, KMessageBox::Notify | KMessageBox::WindowModal); if (choice != KMessageBox::ButtonCode::PrimaryAction) { return; } auto cmd = new Commands::RevokeUserIDCommand(userId); cmd->setParentWidget(q); connect(cmd, &Command::finished, q, [this]() { userIDTable->setEnabled(true); // the Revoke User ID button will be updated by the key update Q_EMIT q->updateKey(); }); userIDTable->setEnabled(false); revokeUserIDBtn->setEnabled(false); cmd->start(); } void UserIdsWidget::Private::revokeSelectedUserID() { const auto userIDs = selectedUserIDs(userIDTable); if (userIDs.size() != 1) { return; } revokeUserID(userIDs.front()); } void UserIdsWidget::Private::userIDTableContextMenuRequested(const QPoint &p) { const auto userIDs = selectedUserIDs(userIDTable); const auto singleUserID = (userIDs.size() == 1) ? userIDs.front() : GpgME::UserID{}; const bool isPrimaryUserID = !singleUserID.isNull() && (userIDTable->selectedItems().front() == userIDTable->topLevelItem(0)); const bool canSignUserIDs = userHasCertificationKey(); const auto isLocalKey = !isRemoteKey(key); const auto keyCanBeCertified = Kleo::canBeCertified(key); auto menu = new QMenu(q); if (key.hasSecret()) { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("favorite")), i18nc("@action:inmenu", "Flag as Primary User ID"), q, [this, singleUserID]() { setPrimaryUserID(singleUserID); }); action->setEnabled(!singleUserID.isNull() // && !isPrimaryUserID // && !Kleo::isRevokedOrExpired(singleUserID) // && canBeUsedForSecretKeyOperations(key)); } { const auto actionText = userIDs.empty() ? i18nc("@action:inmenu", "Certify User IDs...") : i18ncp("@action:inmenu", "Certify User ID...", "Certify User IDs...", userIDs.size()); auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-sign")), actionText, q, [this]() { certifyUserIDs(); }); action->setEnabled(isLocalKey && keyCanBeCertified && canSignUserIDs); } if (Kleo::Commands::RevokeCertificationCommand::isSupported()) { const auto actionText = userIDs.empty() ? i18nc("@action:inmenu", "Revoke Certifications...") : i18ncp("@action:inmenu", "Revoke Certification...", "Revoke Certifications...", userIDs.size()); auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")), actionText, q, [this]() { revokeCertifications(); }); action->setEnabled(isLocalKey && canSignUserIDs); } #ifdef MAILAKONADI_ENABLED if (key.hasSecret()) { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")), i18nc("@action:inmenu", "Publish at Mail Provider ..."), q, [this, singleUserID]() { auto cmd = new Kleo::Commands::ExportOpenPGPCertToProviderCommand(singleUserID); userIDTable->setEnabled(false); connect(cmd, &Kleo::Commands::ExportOpenPGPCertToProviderCommand::finished, q, [this]() { userIDTable->setEnabled(true); }); cmd->start(); }); action->setEnabled(!singleUserID.isNull()); } #endif // MAILAKONADI_ENABLED { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")), i18nc("@action:inmenu", "Revoke User ID"), q, [this, singleUserID]() { revokeUserID(singleUserID); }); action->setEnabled(!singleUserID.isNull() && canCreateCertifications(key) && canRevokeUserID(singleUserID)); } connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); menu->popup(userIDTable->viewport()->mapToGlobal(p)); } void UserIdsWidget::setUpdateInProgress(bool updateInProgress) { d->updateInProgress = updateInProgress; } #include "moc_useridswidget.cpp" diff --git a/src/dialogs/weboftrustwidget.cpp b/src/dialogs/weboftrustwidget.cpp index 2626d6a7e..cd4f32e89 100644 --- a/src/dialogs/weboftrustwidget.cpp +++ b/src/dialogs/weboftrustwidget.cpp @@ -1,433 +1,430 @@ /* 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/importcertificatefromkeyservercommand.h" #include "commands/revokecertificationcommand.h" #include "utils/tags.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; Q_DECLARE_METATYPE(GpgME::UserID) 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; 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; public: Private(WebOfTrustWidget *qq) : q{qq} { certificationsModel.enableRemarks(Tags::tagsEnabled()); auto vLay = new QVBoxLayout(q); vLay->setContentsMargins({}); - vLay->setSpacing(0); certificationsTV = new TreeView{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)); } 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 separator = new KSeparator(q); - vLay->addWidget(separator); auto bbox = new QHBoxLayout; - bbox->setSpacing(q->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing)); 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())); }); 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 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 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(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; } 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(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", "An error occurred while loading the certifications: " "%1", Formatting::errorAsString(result.error())), i18nc("@title", "Certifications Loading Failed")); } d->keyListJob = nullptr; }