diff --git a/autotests/editdirectoryservicedialogtest.cpp b/autotests/editdirectoryservicedialogtest.cpp index a5d5c63d1..ac7386b4d 100644 --- a/autotests/editdirectoryservicedialogtest.cpp +++ b/autotests/editdirectoryservicedialogtest.cpp @@ -1,597 +1,596 @@ /* autotests/editdirectoryservicedialogtest.cpp This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include using namespace Kleo; namespace QTest { template<> char *toString(const KeyserverAuthentication &authentication) { return QTest::toString(static_cast(authentication)); } template<> char *toString(const KeyserverConnection &connection) { return QTest::toString(static_cast(connection)); } } #define ASSERT_HOST_IS(expected) \ do { \ const auto w = dialog->findChild(QStringLiteral("hostEdit")); \ QVERIFY(w); \ QCOMPARE(w->text(), expected); \ } while (false) #define ASSERT_PORT_IS(expected) \ do { \ const auto w = dialog->findChild(QStringLiteral("portSpinBox")); \ QVERIFY(w); \ QCOMPARE(w->value(), expected); \ } while (false) #define ASSERT_USE_DEFAULT_PORT_IS(expected) \ do { \ const auto w = dialog->findChild(QStringLiteral("useDefaultPortCheckBox")); \ QVERIFY(w); \ QCOMPARE(w->isChecked(), expected); \ } while (false) #define ASSERT_AUTHENTICATION_IS(expected) \ do { \ const auto w = dialog->findChild(QStringLiteral("authenticationGroup")); \ QVERIFY(w); \ QCOMPARE(w->checkedId(), static_cast(expected)); \ } while (false) #define ASSERT_USER_IS(expected) \ do { \ const auto w = dialog->findChild(QStringLiteral("userEdit")); \ QVERIFY(w); \ QCOMPARE(w->text(), expected); \ } while (false) #define ASSERT_PASSWORD_IS(expected) \ do { \ const auto w = dialog->findChild(QStringLiteral("passwordEdit")); \ QVERIFY(w); \ QCOMPARE(w->password(), expected); \ } while (false) #define ASSERT_CONNECTION_IS(expected) \ do { \ const auto w = dialog->findChild(QStringLiteral("connectionGroup")); \ QVERIFY(w); \ QCOMPARE(w->checkedId(), static_cast(expected)); \ } while (false) #define ASSERT_BASE_DN_IS(expected) \ do { \ const auto w = dialog->findChild(QStringLiteral("baseDnEdit")); \ QVERIFY(w); \ QCOMPARE(w->text(), expected); \ } while (false) #define ASSERT_ADDITONAL_FLAGS_ARE(expected) \ do { \ const auto w = dialog->findChild(QStringLiteral("additionalFlagsEdit")); \ QVERIFY(w); \ QCOMPARE(w->text(), expected); \ } while (false) #define ASSERT_WIDGET_IS_ENABLED(objectName) \ do { \ const auto w = dialog->findChild(QStringLiteral(objectName)); \ QVERIFY(w); \ QVERIFY(w->isEnabled()); \ } while (false) #define ASSERT_WIDGET_IS_DISABLED(objectName) \ do { \ const auto w = dialog->findChild(QStringLiteral(objectName)); \ QVERIFY(w); \ QVERIFY(!w->isEnabled()); \ } while (false) #define ASSERT_ADVANCED_SETTINGS_ARE_EXPANDED() \ do { \ const auto w = dialog->findChild(QStringLiteral("advancedSettings")); \ QVERIFY(w); \ QVERIFY(w->isExpanded()); \ } while (false) #define ASSERT_ADVANCED_SETTINGS_ARE_COLLAPSED() \ do { \ const auto w = dialog->findChild(QStringLiteral("advancedSettings")); \ QVERIFY(w); \ QVERIFY(!w->isExpanded()); \ } while (false) #define ASSERT_OK_BUTTON_IS_ENABLED() \ do { \ const auto o = dialog->findChild(QStringLiteral("buttonBox")); \ QVERIFY(o); \ QVERIFY(o->button(QDialogButtonBox::Ok)); \ QVERIFY(o->button(QDialogButtonBox::Ok)->isEnabled()); \ } while (false) #define ASSERT_OK_BUTTON_IS_DISABLED() \ do { \ const auto o = dialog->findChild(QStringLiteral("buttonBox")); \ QVERIFY(o); \ QVERIFY(o->button(QDialogButtonBox::Ok)); \ QVERIFY(!o->button(QDialogButtonBox::Ok)->isEnabled()); \ } while (false) #define WHEN_USER_SETS_LINEEDIT_VALUE_TO(objectName, value) \ do { \ const auto w = dialog->findChild(QStringLiteral(objectName)); \ QVERIFY(w); \ w->selectAll(); \ w->del(); \ QTest::keyClicks(w, value); \ } while (false) #define WHEN_USER_SETS_PASSWORD_TO(objectName, value) \ do { \ const auto w = dialog->findChild(QStringLiteral(objectName)); \ QVERIFY(w); \ w->setPassword(value); \ } while (false) #define WHEN_USER_TOGGLES_BUTTON(objectName) \ do { \ const auto w = dialog->findChild(QStringLiteral(objectName)); \ QVERIFY(w); \ QVERIFY(w->isCheckable()); \ w->toggle(); \ } while (false) #define WHEN_USER_SETS_SPINBOX_VALUE_TO(objectName, value) \ do { \ const auto w = dialog->findChild(QStringLiteral(objectName)); \ QVERIFY(w); \ w->setValue(value); \ } while (false) #define WHEN_USER_SELECTS_BUTTON_WITH_ID_IN_BUTTON_GROUP(objectName, buttonId) \ do { \ const auto w = dialog->findChild(QStringLiteral(objectName)); \ QVERIFY(w); \ const auto button = w->button(buttonId); \ QVERIFY(button); \ button->setChecked(true); \ } while (false) #define WHEN_USER_SELECTS_AUTHENTICATION(authentication) \ do { \ WHEN_USER_SELECTS_BUTTON_WITH_ID_IN_BUTTON_GROUP("authenticationGroup", static_cast(authentication)); \ } while (false) #define WHEN_USER_SELECTS_CONNECTION(connection) \ do { \ WHEN_USER_SELECTS_BUTTON_WITH_ID_IN_BUTTON_GROUP("connectionGroup", static_cast(connection)); \ } while (false) class EditDirectoryServiceDialogTest : public QObject { Q_OBJECT private: std::unique_ptr dialog; private Q_SLOTS: void init() { dialog = std::make_unique(); } void cleanup() { dialog.reset(); } void test__initialization() { dialog->show(); ASSERT_HOST_IS(""); ASSERT_USE_DEFAULT_PORT_IS(true); ASSERT_WIDGET_IS_DISABLED("portSpinBox"); ASSERT_PORT_IS(389); ASSERT_AUTHENTICATION_IS(KeyserverAuthentication::Anonymous); ASSERT_WIDGET_IS_DISABLED("userEdit"); ASSERT_USER_IS(""); ASSERT_WIDGET_IS_DISABLED("passwordEdit"); ASSERT_PASSWORD_IS(""); ASSERT_CONNECTION_IS(KeyserverConnection::Default); ASSERT_ADVANCED_SETTINGS_ARE_COLLAPSED(); ASSERT_BASE_DN_IS(""); ASSERT_ADDITONAL_FLAGS_ARE(""); ASSERT_OK_BUTTON_IS_DISABLED(); } void test__setKeyserver_new_server() { KeyserverConfig keyserver; dialog->setKeyserver(keyserver); dialog->show(); ASSERT_HOST_IS(""); ASSERT_USE_DEFAULT_PORT_IS(true); ASSERT_WIDGET_IS_DISABLED("portSpinBox"); ASSERT_PORT_IS(389); ASSERT_AUTHENTICATION_IS(keyserver.authentication()); ASSERT_WIDGET_IS_DISABLED("userEdit"); ASSERT_USER_IS(""); ASSERT_WIDGET_IS_DISABLED("passwordEdit"); ASSERT_PASSWORD_IS(""); ASSERT_CONNECTION_IS(keyserver.connection()); ASSERT_ADVANCED_SETTINGS_ARE_COLLAPSED(); ASSERT_BASE_DN_IS(""); ASSERT_ADDITONAL_FLAGS_ARE(""); ASSERT_OK_BUTTON_IS_DISABLED(); } void test__setKeyserver_existing_server() { KeyserverConfig keyserver; keyserver.setHost(QStringLiteral("ldap.example.com")); dialog->setKeyserver(keyserver); dialog->show(); ASSERT_HOST_IS("ldap.example.com"); ASSERT_OK_BUTTON_IS_ENABLED(); } void test__setKeyserver_anonymous_ldap_server() { KeyserverConfig keyserver; keyserver.setAuthentication(KeyserverAuthentication::Anonymous); dialog->setKeyserver(keyserver); dialog->show(); ASSERT_AUTHENTICATION_IS(KeyserverAuthentication::Anonymous); ASSERT_WIDGET_IS_DISABLED("userEdit"); ASSERT_WIDGET_IS_DISABLED("passwordEdit"); } void test__setKeyserver_authentication_via_active_directory() { KeyserverConfig keyserver; keyserver.setAuthentication(KeyserverAuthentication::ActiveDirectory); dialog->setKeyserver(keyserver); dialog->show(); ASSERT_AUTHENTICATION_IS(KeyserverAuthentication::ActiveDirectory); ASSERT_WIDGET_IS_DISABLED("userEdit"); ASSERT_WIDGET_IS_DISABLED("passwordEdit"); } void test__setKeyserver_authentication_with_password() { KeyserverConfig keyserver; keyserver.setHost(QStringLiteral("ldap.example.com")); keyserver.setAuthentication(KeyserverAuthentication::Password); keyserver.setUser(QStringLiteral("bind dn")); keyserver.setPassword(QStringLiteral("abc123")); dialog->setKeyserver(keyserver); dialog->show(); ASSERT_AUTHENTICATION_IS(KeyserverAuthentication::Password); ASSERT_WIDGET_IS_ENABLED("userEdit"); ASSERT_USER_IS("bind dn"); ASSERT_WIDGET_IS_ENABLED("passwordEdit"); ASSERT_PASSWORD_IS("abc123"); ASSERT_OK_BUTTON_IS_ENABLED(); } void test__setKeyserver_authentication_with_password_requires_user() { KeyserverConfig keyserver; keyserver.setHost(QStringLiteral("ldap.example.com")); keyserver.setAuthentication(KeyserverAuthentication::Password); keyserver.setPassword(QStringLiteral("abc123")); dialog->setKeyserver(keyserver); dialog->show(); ASSERT_AUTHENTICATION_IS(KeyserverAuthentication::Password); ASSERT_USER_IS(""); ASSERT_PASSWORD_IS("abc123"); ASSERT_OK_BUTTON_IS_DISABLED(); } void test__setKeyserver_authentication_with_password_requires_password() { KeyserverConfig keyserver; keyserver.setHost(QStringLiteral("ldap.example.com")); keyserver.setAuthentication(KeyserverAuthentication::Password); keyserver.setUser(QStringLiteral("bind dn")); dialog->setKeyserver(keyserver); dialog->show(); ASSERT_AUTHENTICATION_IS(KeyserverAuthentication::Password); ASSERT_USER_IS("bind dn"); ASSERT_PASSWORD_IS(""); ASSERT_OK_BUTTON_IS_DISABLED(); } void test__setKeyserver_plain_connection() { KeyserverConfig keyserver; keyserver.setConnection(KeyserverConnection::Plain); dialog->setKeyserver(keyserver); dialog->show(); ASSERT_USE_DEFAULT_PORT_IS(true); ASSERT_PORT_IS(389); ASSERT_CONNECTION_IS(KeyserverConnection::Plain); } void test__setKeyserver_starttls_connection() { KeyserverConfig keyserver; keyserver.setConnection(KeyserverConnection::UseSTARTTLS); dialog->setKeyserver(keyserver); dialog->show(); ASSERT_USE_DEFAULT_PORT_IS(true); ASSERT_PORT_IS(389); ASSERT_CONNECTION_IS(KeyserverConnection::UseSTARTTLS); } void test__setKeyserver_ldaptls_connection() { KeyserverConfig keyserver; keyserver.setConnection(KeyserverConnection::TunnelThroughTLS); dialog->setKeyserver(keyserver); dialog->show(); ASSERT_USE_DEFAULT_PORT_IS(true); ASSERT_PORT_IS(636); ASSERT_CONNECTION_IS(KeyserverConnection::TunnelThroughTLS); } void test__setKeyserver_non_default_port() { KeyserverConfig keyserver; keyserver.setPort(1234); dialog->setKeyserver(keyserver); dialog->show(); ASSERT_USE_DEFAULT_PORT_IS(false); ASSERT_WIDGET_IS_ENABLED("portSpinBox"); ASSERT_PORT_IS(1234); } void test__setKeyserver_base_dn() { KeyserverConfig keyserver; keyserver.setLdapBaseDn(QStringLiteral("o=Organization,c=DE")); dialog->setKeyserver(keyserver); dialog->show(); ASSERT_ADVANCED_SETTINGS_ARE_EXPANDED(); ASSERT_BASE_DN_IS("o=Organization,c=DE"); } void test__setKeyserver_additional_flags() { KeyserverConfig keyserver; keyserver.setAdditionalFlags({QStringLiteral("ldaps"), QStringLiteral("foo")}); dialog->setKeyserver(keyserver); dialog->show(); ASSERT_ADVANCED_SETTINGS_ARE_EXPANDED(); ASSERT_ADDITONAL_FLAGS_ARE("ldaps,foo"); } void test__user_sets_or_clears_host() { dialog->show(); ASSERT_OK_BUTTON_IS_DISABLED(); WHEN_USER_SETS_LINEEDIT_VALUE_TO("hostEdit", "ldap.example.com"); ASSERT_OK_BUTTON_IS_ENABLED(); WHEN_USER_SETS_LINEEDIT_VALUE_TO("hostEdit", ""); ASSERT_OK_BUTTON_IS_DISABLED(); } void test__user_enables_or_disables_use_of_default_port() { dialog->show(); ASSERT_USE_DEFAULT_PORT_IS(true); ASSERT_WIDGET_IS_DISABLED("portSpinBox"); ASSERT_PORT_IS(389); WHEN_USER_TOGGLES_BUTTON("useDefaultPortCheckBox"); ASSERT_WIDGET_IS_ENABLED("portSpinBox"); ASSERT_PORT_IS(389); WHEN_USER_SETS_SPINBOX_VALUE_TO("portSpinBox", 1234); ASSERT_PORT_IS(1234); WHEN_USER_TOGGLES_BUTTON("useDefaultPortCheckBox"); ASSERT_USE_DEFAULT_PORT_IS(true); ASSERT_WIDGET_IS_DISABLED("portSpinBox"); ASSERT_PORT_IS(389); } void test__user_changes_authentication() { dialog->show(); WHEN_USER_SETS_LINEEDIT_VALUE_TO("hostEdit", "ldap.example.com"); ASSERT_AUTHENTICATION_IS(KeyserverAuthentication::Anonymous); ASSERT_WIDGET_IS_DISABLED("userEdit"); ASSERT_WIDGET_IS_DISABLED("passwordEdit"); ASSERT_OK_BUTTON_IS_ENABLED(); WHEN_USER_SELECTS_AUTHENTICATION(KeyserverAuthentication::ActiveDirectory); ASSERT_WIDGET_IS_DISABLED("userEdit"); ASSERT_WIDGET_IS_DISABLED("passwordEdit"); ASSERT_OK_BUTTON_IS_ENABLED(); WHEN_USER_SELECTS_AUTHENTICATION(KeyserverAuthentication::Password); ASSERT_WIDGET_IS_ENABLED("userEdit"); ASSERT_WIDGET_IS_ENABLED("passwordEdit"); ASSERT_OK_BUTTON_IS_DISABLED(); WHEN_USER_SELECTS_AUTHENTICATION(KeyserverAuthentication::Anonymous); ASSERT_WIDGET_IS_DISABLED("userEdit"); ASSERT_WIDGET_IS_DISABLED("passwordEdit"); ASSERT_OK_BUTTON_IS_ENABLED(); } void test__user_changes_user_and_password() { dialog->show(); WHEN_USER_SETS_LINEEDIT_VALUE_TO("hostEdit", "ldap.example.com"); WHEN_USER_SELECTS_AUTHENTICATION(KeyserverAuthentication::Password); ASSERT_WIDGET_IS_ENABLED("userEdit"); ASSERT_WIDGET_IS_ENABLED("passwordEdit"); ASSERT_OK_BUTTON_IS_DISABLED(); WHEN_USER_SETS_LINEEDIT_VALUE_TO("userEdit", "user"); ASSERT_OK_BUTTON_IS_DISABLED(); WHEN_USER_SETS_PASSWORD_TO("passwordEdit", "abc123"); ASSERT_OK_BUTTON_IS_ENABLED(); WHEN_USER_SETS_LINEEDIT_VALUE_TO("userEdit", ""); ASSERT_OK_BUTTON_IS_DISABLED(); WHEN_USER_SETS_LINEEDIT_VALUE_TO("userEdit", "user"); ASSERT_OK_BUTTON_IS_ENABLED(); } void test__user_changes_connection() { dialog->show(); ASSERT_CONNECTION_IS(KeyserverConnection::Default); ASSERT_USE_DEFAULT_PORT_IS(true); ASSERT_PORT_IS(389); WHEN_USER_SELECTS_CONNECTION(KeyserverConnection::TunnelThroughTLS); ASSERT_PORT_IS(636); WHEN_USER_SELECTS_CONNECTION(KeyserverConnection::Plain); ASSERT_PORT_IS(389); WHEN_USER_SELECTS_CONNECTION(KeyserverConnection::TunnelThroughTLS); ASSERT_PORT_IS(636); WHEN_USER_SELECTS_CONNECTION(KeyserverConnection::UseSTARTTLS); ASSERT_PORT_IS(389); WHEN_USER_TOGGLES_BUTTON("useDefaultPortCheckBox"); ASSERT_USE_DEFAULT_PORT_IS(false); WHEN_USER_SETS_SPINBOX_VALUE_TO("portSpinBox", 1234); WHEN_USER_SELECTS_CONNECTION(KeyserverConnection::TunnelThroughTLS); ASSERT_PORT_IS(1234); WHEN_USER_SELECTS_CONNECTION(KeyserverConnection::UseSTARTTLS); ASSERT_PORT_IS(1234); WHEN_USER_TOGGLES_BUTTON("useDefaultPortCheckBox"); ASSERT_USE_DEFAULT_PORT_IS(true); ASSERT_PORT_IS(389); } void test__result() { dialog->show(); WHEN_USER_SETS_LINEEDIT_VALUE_TO("hostEdit", " ldap.example.com "); QCOMPARE(dialog->keyserver().host(), "ldap.example.com"); QCOMPARE(dialog->keyserver().port(), -1); WHEN_USER_TOGGLES_BUTTON("useDefaultPortCheckBox"); QCOMPARE(dialog->keyserver().port(), 389); WHEN_USER_SETS_SPINBOX_VALUE_TO("portSpinBox", 1234); QCOMPARE(dialog->keyserver().port(), 1234); WHEN_USER_SELECTS_AUTHENTICATION(KeyserverAuthentication::Anonymous); QCOMPARE(dialog->keyserver().authentication(), KeyserverAuthentication::Anonymous); WHEN_USER_SELECTS_AUTHENTICATION(KeyserverAuthentication::ActiveDirectory); QCOMPARE(dialog->keyserver().authentication(), KeyserverAuthentication::ActiveDirectory); WHEN_USER_SELECTS_AUTHENTICATION(KeyserverAuthentication::Password); QCOMPARE(dialog->keyserver().authentication(), KeyserverAuthentication::Password); QCOMPARE(dialog->keyserver().user(), ""); WHEN_USER_SETS_LINEEDIT_VALUE_TO("userEdit", " user "); QCOMPARE(dialog->keyserver().user(), "user"); QCOMPARE(dialog->keyserver().password(), ""); WHEN_USER_SETS_PASSWORD_TO("passwordEdit", " abc123 "); QCOMPARE(dialog->keyserver().password(), " abc123 "); // the entered password is not trimmed WHEN_USER_SELECTS_CONNECTION(KeyserverConnection::Default); QCOMPARE(dialog->keyserver().connection(), KeyserverConnection::Default); WHEN_USER_SELECTS_CONNECTION(KeyserverConnection::Plain); QCOMPARE(dialog->keyserver().connection(), KeyserverConnection::Plain); WHEN_USER_SELECTS_CONNECTION(KeyserverConnection::UseSTARTTLS); QCOMPARE(dialog->keyserver().connection(), KeyserverConnection::UseSTARTTLS); WHEN_USER_SELECTS_CONNECTION(KeyserverConnection::TunnelThroughTLS); QCOMPARE(dialog->keyserver().connection(), KeyserverConnection::TunnelThroughTLS); QCOMPARE(dialog->keyserver().ldapBaseDn(), ""); WHEN_USER_SETS_LINEEDIT_VALUE_TO("baseDnEdit", " o=Organization,c=DE "); QCOMPARE(dialog->keyserver().ldapBaseDn(), "o=Organization,c=DE"); QCOMPARE(dialog->keyserver().additionalFlags(), {}); WHEN_USER_SETS_LINEEDIT_VALUE_TO("additionalFlagsEdit", " flag1 , flag 2 "); const QStringList expectedFlags{"flag1", "flag 2"}; QCOMPARE(dialog->keyserver().additionalFlags(), expectedFlags); } }; QTEST_MAIN(EditDirectoryServiceDialogTest) #include "editdirectoryservicedialogtest.moc" diff --git a/src/models/keycache.cpp b/src/models/keycache.cpp index b697087e1..ef51c796e 100644 --- a/src/models/keycache.cpp +++ b/src/models/keycache.cpp @@ -1,1737 +1,1736 @@ /* -*- mode: c++; c-basic-offset:4 -*- models/keycache.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007, 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-FileCopyrightText: 2020, 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keycache.h" #include "keycache_p.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 using namespace std::chrono_literals; using namespace Kleo; using namespace GpgME; using namespace KMime::Types; static const unsigned int hours2ms = 1000 * 60 * 60; // // // KeyCache // // namespace { make_comparator_str(ByEMail, .first.c_str()); } class Kleo::KeyCacheAutoRefreshSuspension { KeyCacheAutoRefreshSuspension() { qCDebug(LIBKLEO_LOG) << __func__; auto cache = KeyCache::mutableInstance(); cache->enableFileSystemWatcher(false); m_refreshInterval = cache->refreshInterval(); cache->setRefreshInterval(0); cache->cancelKeyListing(); m_cache = cache; } public: ~KeyCacheAutoRefreshSuspension() { qCDebug(LIBKLEO_LOG) << __func__; if (auto cache = m_cache.lock()) { cache->enableFileSystemWatcher(true); cache->setRefreshInterval(m_refreshInterval); } } static std::shared_ptr instance() { static std::weak_ptr self; if (auto s = self.lock()) { return s; } else { s = std::shared_ptr{new KeyCacheAutoRefreshSuspension{}}; self = s; return s; } } private: std::weak_ptr m_cache; int m_refreshInterval = 0; }; class KeyCache::Private { friend class ::Kleo::KeyCache; KeyCache *const q; public: explicit Private(KeyCache *qq) : q(qq) , m_refreshInterval(1) , m_initalized(false) , m_pgpOnly(true) , m_remarks_enabled(false) { connect(&m_autoKeyListingTimer, &QTimer::timeout, q, [this]() { q->startKeyListing(); }); updateAutoKeyListingTimer(); } ~Private() { if (m_refreshJob) { m_refreshJob->cancel(); } } template class Op> class Comp> std::vector::const_iterator find(const std::vector &keys, const char *key) const { ensureCachePopulated(); const auto it = std::lower_bound(keys.begin(), keys.end(), key, Comp()); if (it == keys.end() || Comp()(*it, key)) { return it; } else { return keys.end(); } } template class Op> class Comp> std::vector::const_iterator find(const std::vector &keys, const char *key) const { ensureCachePopulated(); const auto it = std::lower_bound(keys.begin(), keys.end(), key, Comp()); if (it == keys.end() || Comp()(*it, key)) { return it; } else { return keys.end(); } } std::vector::const_iterator find_fpr(const char *fpr) const { return find<_detail::ByFingerprint>(by.fpr, fpr); } std::pair>::const_iterator, std::vector>::const_iterator> find_email(const char *email) const { ensureCachePopulated(); return std::equal_range(by.email.begin(), by.email.end(), email, ByEMail()); } std::vector find_mailbox(const QString &email, bool sign) const; std::vector::const_iterator find_keygrip(const char *keygrip) const { return find<_detail::ByKeyGrip>(by.keygrip, keygrip); } std::vector::const_iterator find_subkeyid(const char *subkeyid) const { return find<_detail::ByKeyID>(by.subkeyid, subkeyid); } std::vector::const_iterator find_keyid(const char *keyid) const { return find<_detail::ByKeyID>(by.keyid, keyid); } std::vector::const_iterator find_shortkeyid(const char *shortkeyid) const { return find<_detail::ByShortKeyID>(by.shortkeyid, shortkeyid); } std::pair::const_iterator, std::vector::const_iterator> find_subjects(const char *chain_id) const { ensureCachePopulated(); return std::equal_range(by.chainid.begin(), by.chainid.end(), chain_id, _detail::ByChainID()); } void refreshJobDone(const KeyListResult &result); void setRefreshInterval(int interval) { m_refreshInterval = interval; updateAutoKeyListingTimer(); } int refreshInterval() const { return m_refreshInterval; } void updateAutoKeyListingTimer() { setAutoKeyListingInterval(hours2ms * m_refreshInterval); } void setAutoKeyListingInterval(int ms) { m_autoKeyListingTimer.stop(); m_autoKeyListingTimer.setInterval(ms); if (ms != 0) { m_autoKeyListingTimer.start(); } } void ensureCachePopulated() const; void readGroupsFromGpgConf() { // According to Werner Koch groups are more of a hack to solve // a valid usecase (e.g. several keys defined for an internal mailing list) // that won't make it in the proper keylist interface. And using gpgconf // was the suggested way to support groups. auto conf = QGpgME::cryptoConfig(); if (!conf) { return; } auto entry = getCryptoConfigEntry(conf, "gpg", "group"); if (!entry) { return; } // collect the key fingerprints for all groups read from the configuration QMap fingerprints; const auto stringValueList = entry->stringValueList(); for (const QString &value : stringValueList) { const QStringList split = value.split(QLatin1Char('=')); if (split.size() != 2) { qCDebug(LIBKLEO_LOG) << "Ignoring invalid group config:" << value; continue; } const QString groupName = split[0]; const QString fingerprint = split[1]; fingerprints[groupName].push_back(fingerprint); } // add all groups read from the configuration to the list of groups for (auto it = fingerprints.cbegin(); it != fingerprints.cend(); ++it) { const QString groupName = it.key(); const std::vector groupKeys = q->findByFingerprint(toStdStrings(it.value())); KeyGroup g(groupName, groupName, groupKeys, KeyGroup::GnuPGConfig); m_groups.push_back(g); } } void readGroupsFromGroupsConfig() { Q_ASSERT(m_groupConfig); if (!m_groupConfig) { qCWarning(LIBKLEO_LOG) << __func__ << "group config not set"; return; } m_groups = m_groupConfig->readGroups(); } KeyGroup writeGroupToGroupsConfig(const KeyGroup &group) { Q_ASSERT(m_groupConfig); if (!m_groupConfig) { qCWarning(LIBKLEO_LOG) << __func__ << "group config not set"; return {}; } Q_ASSERT(!group.isNull()); Q_ASSERT(group.source() == KeyGroup::ApplicationConfig); if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) { qCDebug(LIBKLEO_LOG) << __func__ << "group cannot be written to application configuration:" << group; return group; } return m_groupConfig->writeGroup(group); } bool removeGroupFromGroupsConfig(const KeyGroup &group) { Q_ASSERT(m_groupConfig); if (!m_groupConfig) { qCWarning(LIBKLEO_LOG) << __func__ << "group config not set"; return false; } Q_ASSERT(!group.isNull()); Q_ASSERT(group.source() == KeyGroup::ApplicationConfig); if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) { qCDebug(LIBKLEO_LOG) << __func__ << "group cannot be removed from application configuration:" << group; return false; } return m_groupConfig->removeGroup(group); } void updateGroupCache() { // Update Group Keys // this is a quick thing as it only involves reading the config // so no need for a job. m_groups.clear(); if (m_groupsEnabled) { readGroupsFromGpgConf(); readGroupsFromGroupsConfig(); } } bool insert(const KeyGroup &group) { Q_ASSERT(!group.isNull()); Q_ASSERT(group.source() == KeyGroup::ApplicationConfig); if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Invalid group:" << group; return false; } const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [group](const auto &g) { return g.source() == group.source() && g.id() == group.id(); }); if (it != m_groups.cend()) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Group already present in list of groups:" << group; return false; } const KeyGroup savedGroup = writeGroupToGroupsConfig(group); if (savedGroup.isNull()) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Writing group" << group.id() << "to config file failed"; return false; } m_groups.push_back(savedGroup); Q_EMIT q->groupAdded(savedGroup); return true; } bool update(const KeyGroup &group) { Q_ASSERT(!group.isNull()); Q_ASSERT(group.source() == KeyGroup::ApplicationConfig); if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Invalid group:" << group; return false; } const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [group](const auto &g) { return g.source() == group.source() && g.id() == group.id(); }); if (it == m_groups.cend()) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Group not found in list of groups:" << group; return false; } const auto groupIndex = std::distance(m_groups.cbegin(), it); const KeyGroup savedGroup = writeGroupToGroupsConfig(group); if (savedGroup.isNull()) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Writing group" << group.id() << "to config file failed"; return false; } m_groups[groupIndex] = savedGroup; Q_EMIT q->groupUpdated(savedGroup); return true; } bool remove(const KeyGroup &group) { Q_ASSERT(!group.isNull()); Q_ASSERT(group.source() == KeyGroup::ApplicationConfig); if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Invalid group:" << group; return false; } const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [group](const auto &g) { return g.source() == group.source() && g.id() == group.id(); }); if (it == m_groups.cend()) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Group not found in list of groups:" << group; return false; } const bool success = removeGroupFromGroupsConfig(group); if (!success) { qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Removing group" << group.id() << "from config file failed"; return false; } m_groups.erase(it); Q_EMIT q->groupRemoved(group); return true; } private: QPointer m_refreshJob; std::vector> m_fsWatchers; QTimer m_autoKeyListingTimer; int m_refreshInterval; struct By { std::vector fpr, keyid, shortkeyid, chainid; std::vector> email; std::vector subkeyid, keygrip; } by; bool m_initalized; bool m_pgpOnly; bool m_remarks_enabled; bool m_groupsEnabled = false; std::shared_ptr m_groupConfig; std::vector m_groups; }; std::shared_ptr KeyCache::instance() { return mutableInstance(); } std::shared_ptr KeyCache::mutableInstance() { static std::weak_ptr self; try { return std::shared_ptr(self); } catch (const std::bad_weak_ptr &) { const std::shared_ptr s(new KeyCache); self = s; return s; } } KeyCache::KeyCache() : QObject() , d(new Private(this)) { } KeyCache::~KeyCache() { } void KeyCache::setGroupsEnabled(bool enabled) { d->m_groupsEnabled = enabled; if (d->m_initalized) { d->updateGroupCache(); } } void KeyCache::setGroupConfig(const std::shared_ptr &groupConfig) { d->m_groupConfig = groupConfig; } void KeyCache::enableFileSystemWatcher(bool enable) { for (const auto &i : std::as_const(d->m_fsWatchers)) { i->setEnabled(enable); } } void KeyCache::setRefreshInterval(int hours) { d->setRefreshInterval(hours); } int KeyCache::refreshInterval() const { return d->refreshInterval(); } std::shared_ptr KeyCache::suspendAutoRefresh() { return KeyCacheAutoRefreshSuspension::instance(); } void KeyCache::reload(GpgME::Protocol /*proto*/) { if (d->m_refreshJob) { return; } d->updateAutoKeyListingTimer(); enableFileSystemWatcher(false); d->m_refreshJob = new RefreshKeysJob(this); connect(d->m_refreshJob.data(), &RefreshKeysJob::done, this, [this](const GpgME::KeyListResult &r) { d->refreshJobDone(r); }); connect(d->m_refreshJob.data(), &RefreshKeysJob::canceled, this, [this]() { d->m_refreshJob.clear(); }); d->m_refreshJob->start(); } void KeyCache::cancelKeyListing() { if (!d->m_refreshJob) { return; } d->m_refreshJob->cancel(); } void KeyCache::addFileSystemWatcher(const std::shared_ptr &watcher) { if (!watcher) { return; } d->m_fsWatchers.push_back(watcher); connect(watcher.get(), &FileSystemWatcher::directoryChanged, this, [this]() { startKeyListing(); }); connect(watcher.get(), &FileSystemWatcher::fileChanged, this, [this]() { startKeyListing(); }); watcher->setEnabled(d->m_refreshJob.isNull()); } void KeyCache::enableRemarks(bool value) { if (!d->m_remarks_enabled && value) { d->m_remarks_enabled = value; if (d->m_initalized && !d->m_refreshJob) { qCDebug(LIBKLEO_LOG) << "Reloading keycache with remarks enabled"; reload(); } else { connect(d->m_refreshJob.data(), &RefreshKeysJob::done, this, [this](const GpgME::KeyListResult &) { qCDebug(LIBKLEO_LOG) << "Reloading keycache with remarks enabled"; QTimer::singleShot(1s, this, [this]() { reload(); }); }); } } else { d->m_remarks_enabled = value; } } bool KeyCache::remarksEnabled() const { return d->m_remarks_enabled; } void KeyCache::Private::refreshJobDone(const KeyListResult &result) { m_refreshJob.clear(); q->enableFileSystemWatcher(true); m_initalized = true; updateGroupCache(); Q_EMIT q->keyListingDone(result); } const Key &KeyCache::findByFingerprint(const char *fpr) const { const std::vector::const_iterator it = d->find_fpr(fpr); if (it == d->by.fpr.end()) { static const Key null; return null; } else { return *it; } } const Key &KeyCache::findByFingerprint(const std::string &fpr) const { return findByFingerprint(fpr.c_str()); } std::vector KeyCache::findByFingerprint(const std::vector &fprs) const { std::vector keys; keys.reserve(fprs.size()); for (const auto &fpr : fprs) { const Key key = findByFingerprint(fpr.c_str()); if (key.isNull()) { qCDebug(LIBKLEO_LOG) << __func__ << "Ignoring unknown key with fingerprint:" << fpr.c_str(); continue; } keys.push_back(key); } return keys; } std::vector KeyCache::findByEMailAddress(const char *email) const { const auto pair = d->find_email(email); std::vector result; result.reserve(std::distance(pair.first, pair.second)); std::transform(pair.first, pair.second, std::back_inserter(result), [](const std::pair &pair) { return pair.second; }); return result; } std::vector KeyCache::findByEMailAddress(const std::string &email) const { return findByEMailAddress(email.c_str()); } const Key &KeyCache::findByShortKeyID(const char *id) const { const std::vector::const_iterator it = d->find_shortkeyid(id); if (it != d->by.shortkeyid.end()) { return *it; } static const Key null; return null; } const Key &KeyCache::findByShortKeyID(const std::string &id) const { return findByShortKeyID(id.c_str()); } const Key &KeyCache::findByKeyIDOrFingerprint(const char *id) const { { // try by.fpr first: const std::vector::const_iterator it = d->find_fpr(id); if (it != d->by.fpr.end()) { return *it; } } { // try by.keyid next: const std::vector::const_iterator it = d->find_keyid(id); if (it != d->by.keyid.end()) { return *it; } } static const Key null; return null; } const Key &KeyCache::findByKeyIDOrFingerprint(const std::string &id) const { return findByKeyIDOrFingerprint(id.c_str()); } std::vector KeyCache::findByKeyIDOrFingerprint(const std::vector &ids) const { std::vector keyids; std::remove_copy_if(ids.begin(), ids.end(), std::back_inserter(keyids), [](const std::string &str) { return !str.c_str() || !*str.c_str(); }); // this is just case-insensitive string search: std::sort(keyids.begin(), keyids.end(), _detail::ByFingerprint()); std::vector result; result.reserve(keyids.size()); // dups shouldn't happen d->ensureCachePopulated(); kdtools::set_intersection(d->by.fpr.begin(), d->by.fpr.end(), keyids.begin(), keyids.end(), std::back_inserter(result), _detail::ByFingerprint()); if (result.size() < keyids.size()) { // note that By{Fingerprint,KeyID,ShortKeyID} define the same // order for _strings_ kdtools::set_intersection(d->by.keyid.begin(), d->by.keyid.end(), keyids.begin(), keyids.end(), std::back_inserter(result), _detail::ByKeyID()); } // duplicates shouldn't happen, but make sure nonetheless: std::sort(result.begin(), result.end(), _detail::ByFingerprint()); result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint()), result.end()); // we skip looking into short key ids here, as it's highly // unlikely they're used for this purpose. We might need to revise // this decision, but only after testing. return result; } const Subkey &KeyCache::findSubkeyByKeyGrip(const char *grip, Protocol protocol) const { static const Subkey null; d->ensureCachePopulated(); const auto range = std::equal_range(d->by.keygrip.begin(), d->by.keygrip.end(), grip, _detail::ByKeyGrip()); if (range.first == range.second) { return null; } else if (protocol == UnknownProtocol) { return *range.first; } else { for (auto it = range.first; it != range.second; ++it) { if (it->parent().protocol() == protocol) { return *it; } } } return null; } const Subkey &KeyCache::findSubkeyByKeyGrip(const std::string &grip, Protocol protocol) const { return findSubkeyByKeyGrip(grip.c_str(), protocol); } std::vector KeyCache::findSubkeysByKeyID(const std::vector &ids) const { std::vector sorted; sorted.reserve(ids.size()); std::remove_copy_if(ids.begin(), ids.end(), std::back_inserter(sorted), [](const std::string &str) { return !str.c_str() || !*str.c_str(); }); std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID()); std::vector result; d->ensureCachePopulated(); kdtools::set_intersection(d->by.subkeyid.begin(), d->by.subkeyid.end(), sorted.begin(), sorted.end(), std::back_inserter(result), _detail::ByKeyID()); return result; } std::vector KeyCache::findRecipients(const DecryptionResult &res) const { std::vector keyids; const auto recipients = res.recipients(); for (const DecryptionResult::Recipient &r : recipients) { if (const char *kid = r.keyID()) { keyids.push_back(kid); } } const std::vector subkeys = findSubkeysByKeyID(keyids); std::vector result; result.reserve(subkeys.size()); std::transform(subkeys.begin(), subkeys.end(), std::back_inserter(result), std::mem_fn(&Subkey::parent)); std::sort(result.begin(), result.end(), _detail::ByFingerprint()); result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint()), result.end()); return result; } std::vector KeyCache::findSigners(const VerificationResult &res) const { std::vector fprs; const auto signatures = res.signatures(); for (const Signature &s : signatures) { if (const char *fpr = s.fingerprint()) { fprs.push_back(fpr); } } return findByKeyIDOrFingerprint(fprs); } std::vector KeyCache::findSigningKeysByMailbox(const QString &mb) const { return d->find_mailbox(mb, true); } std::vector KeyCache::findEncryptionKeysByMailbox(const QString &mb) const { return d->find_mailbox(mb, false); } namespace { #define DO(op, meth, meth2) \ if (op key.meth()) { \ } else { \ qDebug("rejecting for signing: %s: %s", #meth2, key.primaryFingerprint()); \ return false; \ } #define ACCEPT(meth) DO(!!, meth, !meth) #define REJECT(meth) DO(!, meth, meth) struct ready_for_signing { bool operator()(const Key &key) const { #if 1 ACCEPT(hasSecret); ACCEPT(canReallySign); REJECT(isRevoked); REJECT(isExpired); REJECT(isDisabled); REJECT(isInvalid); return true; #else return key.hasSecret() && key.canReallySign() && !key.isRevoked() && !key.isExpired() && !key.isDisabled() && !key.isInvalid(); #endif #undef DO } }; #define DO(op, meth, meth2) \ if (op key.meth()) { \ } else { \ qDebug("rejecting for encrypting: %s: %s", #meth2, key.primaryFingerprint()); \ return false; \ } struct ready_for_encryption { bool operator()(const Key &key) const { #if 1 ACCEPT(canEncrypt); REJECT(isRevoked); REJECT(isExpired); REJECT(isDisabled); REJECT(isInvalid); return true; #else return key.canEncrypt() && !key.isRevoked() && !key.isExpired() && !key.isDisabled() && !key.isInvalid(); #endif } #undef DO #undef ACCEPT #undef REJECT }; } std::vector KeyCache::Private::find_mailbox(const QString &email, bool sign) const { if (email.isEmpty()) { return std::vector(); } const auto pair = find_email(email.toUtf8().constData()); std::vector result; result.reserve(std::distance(pair.first, pair.second)); if (sign) { kdtools::copy_2nd_if(pair.first, pair.second, std::back_inserter(result), ready_for_signing()); } else { kdtools::copy_2nd_if(pair.first, pair.second, std::back_inserter(result), ready_for_encryption()); } return result; } std::vector KeyCache::findSubjects(const GpgME::Key &key, Options options) const { return findSubjects(std::vector(1, key), options); } std::vector KeyCache::findSubjects(const std::vector &keys, Options options) const { return findSubjects(keys.begin(), keys.end(), options); } std::vector KeyCache::findSubjects(std::vector::const_iterator first, std::vector::const_iterator last, Options options) const { if (first == last) { return std::vector(); } std::vector result; while (first != last) { const auto pair = d->find_subjects(first->primaryFingerprint()); result.insert(result.end(), pair.first, pair.second); ++first; } std::sort(result.begin(), result.end(), _detail::ByFingerprint()); result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint()), result.end()); if (options & RecursiveSearch) { const std::vector furtherSubjects = findSubjects(result, options); std::vector combined; combined.reserve(result.size() + furtherSubjects.size()); std::merge(result.begin(), result.end(), furtherSubjects.begin(), furtherSubjects.end(), std::back_inserter(combined), _detail::ByFingerprint()); combined.erase(std::unique(combined.begin(), combined.end(), _detail::ByFingerprint()), combined.end()); result.swap(combined); } return result; } std::vector KeyCache::findIssuers(const Key &key, Options options) const { std::vector result; if (key.isNull()) { return result; } if (options & IncludeSubject) { result.push_back(key); } if (key.isRoot()) { return result; } const Key &issuer = findByFingerprint(key.chainID()); if (issuer.isNull()) { return result; } result.push_back(issuer); if (!(options & RecursiveSearch)) { return result; } while (true) { const Key &issuer = findByFingerprint(result.back().chainID()); if (issuer.isNull()) { break; } const bool chainAlreadyContainsIssuer = Kleo::contains_if(result, [issuer](const auto &key) { return _detail::ByFingerprint()(issuer, key); }); // we also add the issuer if the chain already contains it, so that // the user can spot the recursion result.push_back(issuer); if (issuer.isRoot() || chainAlreadyContainsIssuer) { break; } } return result; } static std::string email(const UserID &uid) { // Prefer the gnupg normalized one const std::string addr = uid.addrSpec(); if (!addr.empty()) { return addr; } const std::string email = uid.email(); if (email.empty()) { return DN(uid.id())[QStringLiteral("EMAIL")].trimmed().toUtf8().constData(); } if (email[0] == '<' && email[email.size() - 1] == '>') { return email.substr(1, email.size() - 2); } else { return email; } } static std::vector emails(const Key &key) { std::vector emails; const auto userIDs = key.userIDs(); for (const UserID &uid : userIDs) { const std::string e = email(uid); if (!e.empty()) { emails.push_back(e); } } std::sort(emails.begin(), emails.end(), ByEMail()); emails.erase(std::unique(emails.begin(), emails.end(), ByEMail()), emails.end()); return emails; } void KeyCache::remove(const Key &key) { if (key.isNull()) { return; } const char *fpr = key.primaryFingerprint(); if (!fpr) { return; } Q_EMIT aboutToRemove(key); { const auto range = std::equal_range(d->by.fpr.begin(), d->by.fpr.end(), fpr, _detail::ByFingerprint()); d->by.fpr.erase(range.first, range.second); } if (const char *keyid = key.keyID()) { const auto range = std::equal_range(d->by.keyid.begin(), d->by.keyid.end(), keyid, _detail::ByKeyID()); const auto it = std::remove_if(range.first, range.second, [fpr](const GpgME::Key &key) { return _detail::ByFingerprint()(fpr, key); }); d->by.keyid.erase(it, range.second); } if (const char *shortkeyid = key.shortKeyID()) { const auto range = std::equal_range(d->by.shortkeyid.begin(), d->by.shortkeyid.end(), shortkeyid, _detail::ByShortKeyID()); const auto it = std::remove_if(range.first, range.second, [fpr](const GpgME::Key &key) { return _detail::ByFingerprint()(fpr, key); }); d->by.shortkeyid.erase(it, range.second); } if (const char *chainid = key.chainID()) { const auto range = std::equal_range(d->by.chainid.begin(), d->by.chainid.end(), chainid, _detail::ByChainID()); const auto range2 = std::equal_range(range.first, range.second, fpr, _detail::ByFingerprint()); d->by.chainid.erase(range2.first, range2.second); } const auto emailsKey{emails(key)}; for (const std::string &email : emailsKey) { const auto range = std::equal_range(d->by.email.begin(), d->by.email.end(), email, ByEMail()); const auto it = std::remove_if(range.first, range.second, [fpr](const std::pair &pair) { return qstricmp(fpr, pair.second.primaryFingerprint()) == 0; }); d->by.email.erase(it, range.second); } const auto keySubKeys{key.subkeys()}; for (const Subkey &subkey : keySubKeys) { if (const char *keyid = subkey.keyID()) { const auto range = std::equal_range(d->by.subkeyid.begin(), d->by.subkeyid.end(), keyid, _detail::ByKeyID()); const auto it = std::remove_if(range.first, range.second, [fpr](const Subkey &subkey) { return !qstricmp(fpr, subkey.parent().primaryFingerprint()); }); d->by.subkeyid.erase(it, range.second); } if (const char *keygrip = subkey.keyGrip()) { const auto range = std::equal_range(d->by.keygrip.begin(), d->by.keygrip.end(), keygrip, _detail::ByKeyGrip()); const auto it = std::remove_if(range.first, range.second, [fpr](const Subkey &subkey) { return !qstricmp(fpr, subkey.parent().primaryFingerprint()); }); d->by.keygrip.erase(it, range.second); } } } void KeyCache::remove(const std::vector &keys) { for (const Key &key : keys) { remove(key); } } const std::vector &KeyCache::keys() const { d->ensureCachePopulated(); return d->by.fpr; } std::vector KeyCache::secretKeys() const { std::vector keys = this->keys(); keys.erase(std::remove_if(keys.begin(), keys.end(), [](const Key &key) { return !key.hasSecret(); }), keys.end()); return keys; } KeyGroup KeyCache::group(const QString &id) const { KeyGroup result{}; const auto it = std::find_if(std::cbegin(d->m_groups), std::cend(d->m_groups), [id](const auto &g) { return g.id() == id; }); if (it != std::cend(d->m_groups)) { result = *it; } return result; } std::vector KeyCache::groups() const { d->ensureCachePopulated(); return d->m_groups; } std::vector KeyCache::configurableGroups() const { std::vector groups; groups.reserve(d->m_groups.size()); std::copy_if(d->m_groups.cbegin(), d->m_groups.cend(), std::back_inserter(groups), [](const KeyGroup &group) { return group.source() == KeyGroup::ApplicationConfig; }); return groups; } namespace { bool compareById(const KeyGroup &lhs, const KeyGroup &rhs) { return lhs.id() < rhs.id(); } std::vector sortedById(std::vector groups) { std::sort(groups.begin(), groups.end(), &compareById); return groups; } } void KeyCache::saveConfigurableGroups(const std::vector &groups) { const std::vector oldGroups = sortedById(configurableGroups()); const std::vector newGroups = sortedById(groups); { std::vector removedGroups; std::set_difference(oldGroups.begin(), oldGroups.end(), newGroups.begin(), newGroups.end(), std::back_inserter(removedGroups), &compareById); for (const auto &group : std::as_const(removedGroups)) { qCDebug(LIBKLEO_LOG) << "Removing group" << group; d->remove(group); } } { std::vector updatedGroups; std::set_intersection(newGroups.begin(), newGroups.end(), oldGroups.begin(), oldGroups.end(), std::back_inserter(updatedGroups), &compareById); for (const auto &group : std::as_const(updatedGroups)) { qCDebug(LIBKLEO_LOG) << "Updating group" << group; d->update(group); } } { std::vector addedGroups; std::set_difference(newGroups.begin(), newGroups.end(), oldGroups.begin(), oldGroups.end(), std::back_inserter(addedGroups), &compareById); for (const auto &group : std::as_const(addedGroups)) { qCDebug(LIBKLEO_LOG) << "Adding group" << group; d->insert(group); } } Q_EMIT keysMayHaveChanged(); } bool KeyCache::insert(const KeyGroup &group) { if (!d->insert(group)) { return false; } Q_EMIT keysMayHaveChanged(); return true; } bool KeyCache::update(const KeyGroup &group) { if (!d->update(group)) { return false; } Q_EMIT keysMayHaveChanged(); return true; } bool KeyCache::remove(const KeyGroup &group) { if (!d->remove(group)) { return false; } Q_EMIT keysMayHaveChanged(); return true; } void KeyCache::refresh(const std::vector &keys) { // make this better... clear(); insert(keys); } void KeyCache::insert(const Key &key) { insert(std::vector(1, key)); } namespace { template class Op> class T1, template class Op> class T2> struct lexicographically { using result_type = bool; template bool operator()(const U &lhs, const V &rhs) const { return T1()(lhs, rhs) // || (T1()(lhs, rhs) && T2()(lhs, rhs)); } }; } void KeyCache::insert(const std::vector &keys) { // 1. remove those with empty fingerprints: std::vector sorted; sorted.reserve(keys.size()); std::remove_copy_if(keys.begin(), keys.end(), std::back_inserter(sorted), [](const Key &key) { auto fp = key.primaryFingerprint(); return !fp || !*fp; }); Q_FOREACH (const Key &key, sorted) { remove(key); // this is sub-optimal, but makes implementation from here on much easier } // 2. sort by fingerprint: std::sort(sorted.begin(), sorted.end(), _detail::ByFingerprint()); // 2a. insert into fpr index: std::vector by_fpr; by_fpr.reserve(sorted.size() + d->by.fpr.size()); std::merge(sorted.begin(), sorted.end(), d->by.fpr.begin(), d->by.fpr.end(), std::back_inserter(by_fpr), _detail::ByFingerprint()); // 3. build email index: std::vector> pairs; pairs.reserve(sorted.size()); for (const Key &key : std::as_const(sorted)) { const std::vector emails = ::emails(key); for (const std::string &e : emails) { pairs.push_back(std::make_pair(e, key)); } } std::sort(pairs.begin(), pairs.end(), ByEMail()); // 3a. insert into email index: std::vector> by_email; by_email.reserve(pairs.size() + d->by.email.size()); std::merge(pairs.begin(), pairs.end(), d->by.email.begin(), d->by.email.end(), std::back_inserter(by_email), ByEMail()); // 3.5: stable-sort by chain-id (effectively lexicographically) std::stable_sort(sorted.begin(), sorted.end(), _detail::ByChainID()); // 3.5a: insert into chain-id index: std::vector nonroot; nonroot.reserve(sorted.size()); std::vector by_chainid; by_chainid.reserve(sorted.size() + d->by.chainid.size()); std::copy_if(sorted.cbegin(), sorted.cend(), std::back_inserter(nonroot), [](const Key &key) { return !key.isRoot(); }); std::merge(nonroot.cbegin(), nonroot.cend(), d->by.chainid.cbegin(), d->by.chainid.cend(), std::back_inserter(by_chainid), lexicographically<_detail::ByChainID, _detail::ByFingerprint>()); // 4. sort by key id: std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID()); // 4a. insert into keyid index: std::vector by_keyid; by_keyid.reserve(sorted.size() + d->by.keyid.size()); std::merge(sorted.begin(), sorted.end(), d->by.keyid.begin(), d->by.keyid.end(), std::back_inserter(by_keyid), _detail::ByKeyID()); // 5. sort by short key id: std::sort(sorted.begin(), sorted.end(), _detail::ByShortKeyID()); // 5a. insert into short keyid index: std::vector by_shortkeyid; by_shortkeyid.reserve(sorted.size() + d->by.shortkeyid.size()); std::merge(sorted.begin(), sorted.end(), d->by.shortkeyid.begin(), d->by.shortkeyid.end(), std::back_inserter(by_shortkeyid), _detail::ByShortKeyID()); // 6. build subkey ID index: std::vector subkeys; subkeys.reserve(sorted.size()); for (const Key &key : std::as_const(sorted)) { const auto keySubkeys{key.subkeys()}; for (const Subkey &subkey : keySubkeys) { subkeys.push_back(subkey); } } // 6a sort by key id: std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyID()); // 6b. insert into subkey ID index: std::vector by_subkeyid; by_subkeyid.reserve(subkeys.size() + d->by.subkeyid.size()); std::merge(subkeys.begin(), subkeys.end(), d->by.subkeyid.begin(), d->by.subkeyid.end(), std::back_inserter(by_subkeyid), _detail::ByKeyID()); // 6c. sort by key grip std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyGrip()); // 6d. insert into subkey keygrip index: std::vector by_keygrip; by_keygrip.reserve(subkeys.size() + d->by.keygrip.size()); std::merge(subkeys.begin(), subkeys.end(), d->by.keygrip.begin(), d->by.keygrip.end(), std::back_inserter(by_keygrip), _detail::ByKeyGrip()); // now commit (well, we already removed keys...) by_fpr.swap(d->by.fpr); by_keyid.swap(d->by.keyid); by_shortkeyid.swap(d->by.shortkeyid); by_email.swap(d->by.email); by_subkeyid.swap(d->by.subkeyid); by_keygrip.swap(d->by.keygrip); by_chainid.swap(d->by.chainid); for (const Key &key : std::as_const(sorted)) { d->m_pgpOnly &= key.protocol() == GpgME::OpenPGP; Q_EMIT added(key); } Q_EMIT keysMayHaveChanged(); } void KeyCache::clear() { d->by = Private::By(); } // // // RefreshKeysJob // // class KeyCache::RefreshKeysJob::Private { RefreshKeysJob *const q; public: Private(KeyCache *cache, RefreshKeysJob *qq); void doStart(); Error startKeyListing(GpgME::Protocol protocol); void listAllKeysJobDone(const KeyListResult &res, const std::vector &nextKeys) { std::vector keys; keys.reserve(m_keys.size() + nextKeys.size()); if (m_keys.empty()) { keys = nextKeys; } else { std::merge(m_keys.begin(), m_keys.end(), nextKeys.begin(), nextKeys.end(), std::back_inserter(keys), _detail::ByFingerprint()); } m_keys.swap(keys); jobDone(res); } void emitDone(const KeyListResult &result); void updateKeyCache(); QPointer m_cache; QVector m_jobsPending; std::vector m_keys; KeyListResult m_mergedResult; bool m_canceled; private: void jobDone(const KeyListResult &res); }; KeyCache::RefreshKeysJob::Private::Private(KeyCache *cache, RefreshKeysJob *qq) : q(qq) , m_cache(cache) , m_canceled(false) { Q_ASSERT(m_cache); } void KeyCache::RefreshKeysJob::Private::jobDone(const KeyListResult &result) { if (m_canceled) { q->deleteLater(); return; } QObject *const sender = q->sender(); if (sender) { sender->disconnect(q); } Q_ASSERT(m_jobsPending.size() > 0); m_jobsPending.removeOne(qobject_cast(sender)); m_mergedResult.mergeWith(result); if (m_jobsPending.size() > 0) { return; } updateKeyCache(); emitDone(m_mergedResult); } void KeyCache::RefreshKeysJob::Private::emitDone(const KeyListResult &res) { q->deleteLater(); Q_EMIT q->done(res); } KeyCache::RefreshKeysJob::RefreshKeysJob(KeyCache *cache, QObject *parent) : QObject(parent) , d(new Private(cache, this)) { } KeyCache::RefreshKeysJob::~RefreshKeysJob() { delete d; } void KeyCache::RefreshKeysJob::start() { QTimer::singleShot(0, this, [this]() { d->doStart(); }); } void KeyCache::RefreshKeysJob::cancel() { d->m_canceled = true; std::for_each(d->m_jobsPending.begin(), d->m_jobsPending.end(), std::mem_fn(&QGpgME::ListAllKeysJob::slotCancel)); Q_EMIT canceled(); } void KeyCache::RefreshKeysJob::Private::doStart() { if (m_canceled) { q->deleteLater(); return; } Q_ASSERT(m_jobsPending.size() == 0); m_mergedResult.mergeWith(KeyListResult(startKeyListing(GpgME::OpenPGP))); m_mergedResult.mergeWith(KeyListResult(startKeyListing(GpgME::CMS))); if (m_jobsPending.size() != 0) { return; } const bool hasError = m_mergedResult.error() || m_mergedResult.error().isCanceled(); emitDone(hasError ? m_mergedResult : KeyListResult(Error(GPG_ERR_UNSUPPORTED_OPERATION))); } void KeyCache::RefreshKeysJob::Private::updateKeyCache() { if (!m_cache || m_canceled) { q->deleteLater(); return; } std::vector cachedKeys = m_cache->initialized() ? m_cache->keys() : std::vector(); std::sort(cachedKeys.begin(), cachedKeys.end(), _detail::ByFingerprint()); std::vector keysToRemove; std::set_difference(cachedKeys.begin(), cachedKeys.end(), m_keys.begin(), m_keys.end(), std::back_inserter(keysToRemove), _detail::ByFingerprint()); m_cache->remove(keysToRemove); m_cache->refresh(m_keys); } Error KeyCache::RefreshKeysJob::Private::startKeyListing(GpgME::Protocol proto) { const auto *const protocol = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); if (!protocol) { return Error(); } QGpgME::ListAllKeysJob *const job = protocol->listAllKeysJob(/*includeSigs*/ false, /*validate*/ true); if (!job) { return Error(); } #if 0 aheinecke: 2017.01.12: For unknown reasons the new style connect fails at runtime over library borders into QGpgME from the GpgME repo when cross compiled for Windows and default arguments are used in the Signal. This was tested with gcc 4.9 (Mingw 3.0.2) and we could not find an explanation for this. So until this is fixed or we understand the problem we need to use the old style connect for QGpgME signals. The new style connect of the canceled signal right below works fine. connect(job, &QGpgME::ListAllKeysJob::result, q, [this](const GpgME::KeyListResult &res, const std::vector &keys) { listAllKeysJobDone(res, keys); }); #endif connect(job, SIGNAL(result(GpgME::KeyListResult, std::vector)), q, SLOT(listAllKeysJobDone(GpgME::KeyListResult, std::vector))); connect(q, &RefreshKeysJob::canceled, job, &QGpgME::Job::slotCancel); // Only do this for initialized keycaches to avoid huge waits for // signature notations during initial keylisting. if (proto == GpgME::OpenPGP && m_cache->remarksEnabled() && m_cache->initialized()) { auto ctx = QGpgME::Job::context(job); if (ctx) { ctx->addKeyListMode(KeyListMode::Signatures | KeyListMode::SignatureNotations); } } const Error error = job->start(true); if (!error && !error.isCanceled()) { m_jobsPending.push_back(job); } return error; } bool KeyCache::initialized() const { return d->m_initalized; } void KeyCache::Private::ensureCachePopulated() const { if (!m_initalized) { q->startKeyListing(); QEventLoop loop; loop.connect(q, &KeyCache::keyListingDone, &loop, &QEventLoop::quit); qCDebug(LIBKLEO_LOG) << "Waiting for keycache."; loop.exec(); qCDebug(LIBKLEO_LOG) << "Keycache available."; } } bool KeyCache::pgpOnly() const { return d->m_pgpOnly; } static bool keyIsOk(const Key &k) { return !k.isExpired() && !k.isRevoked() && !k.isInvalid() && !k.isDisabled(); } static bool uidIsOk(const UserID &uid) { return keyIsOk(uid.parent()) && !uid.isRevoked() && !uid.isInvalid(); } static bool subkeyIsOk(const Subkey &s) { return !s.isRevoked() && !s.isInvalid() && !s.isDisabled(); } namespace { time_t creationTimeOfNewestSuitableSubKey(const Key &key, KeyCache::KeyUsage usage) { time_t creationTime = 0; for (const Subkey &s : key.subkeys()) { if (!subkeyIsOk(s)) { continue; } if (usage == KeyCache::KeyUsage::Sign && !s.canSign()) { continue; } if (usage == KeyCache::KeyUsage::Encrypt && !s.canEncrypt()) { continue; } if (s.creationTime() > creationTime) { creationTime = s.creationTime(); } } return creationTime; } struct BestMatch { Key key; UserID uid; time_t creationTime = 0; }; } GpgME::Key KeyCache::findBestByMailBox(const char *addr, GpgME::Protocol proto, KeyUsage usage) const { d->ensureCachePopulated(); if (!addr) { return {}; } // support lookup of email addresses enclosed in angle brackets QByteArray address(addr); if (address[0] == '<' && address[address.size() - 1] == '>') { address = address.mid(1, address.size() - 2); } address = address.toLower(); BestMatch best; for (const Key &k : findByEMailAddress(address.constData())) { if (proto != Protocol::UnknownProtocol && k.protocol() != proto) { continue; } if (usage == KeyUsage::Encrypt && !k.canEncrypt()) { continue; } if (usage == KeyUsage::Sign && (!k.canSign() || !k.hasSecret())) { continue; } const time_t creationTime = creationTimeOfNewestSuitableSubKey(k, usage); if (creationTime == 0) { // key does not have a suitable (and usable) subkey continue; } for (const UserID &u : k.userIDs()) { if (QByteArray::fromStdString(u.addrSpec()).toLower() != address) { // user ID does not match the given email address continue; } if (best.uid.isNull()) { // we have found our first candidate best = {k, u, creationTime}; } else if (!uidIsOk(best.uid) && uidIsOk(u)) { // validity of the new key is better best = {k, u, creationTime}; } else if (!k.isExpired() && best.uid.validity() < u.validity()) { // validity of the new key is better best = {k, u, creationTime}; } else if (best.key.isExpired() && !k.isExpired()) { // validity of the new key is better best = {k, u, creationTime}; } else if (best.uid.validity() == u.validity() && uidIsOk(u) && best.creationTime < creationTime) { // both keys/user IDs have same validity, but the new key is newer best = {k, u, creationTime}; } } } return best.key; } namespace { template bool allKeysAllowUsage(const T &keys, KeyCache::KeyUsage usage) { switch (usage) { case KeyCache::KeyUsage::AnyUsage: return true; case KeyCache::KeyUsage::Sign: return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::canSign)); case KeyCache::KeyUsage::Encrypt: return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::canEncrypt)); case KeyCache::KeyUsage::Certify: return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::canCertify)); case KeyCache::KeyUsage::Authenticate: return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::canAuthenticate)); } qCDebug(LIBKLEO_LOG) << __func__ << "called with invalid usage" << int(usage); return false; } template bool allKeysHaveProtocol(const T &keys, Protocol protocol) { return std::all_of(std::begin(keys), std::end(keys), [protocol](const auto &key) { return key.protocol() == protocol; }); } } KeyGroup KeyCache::findGroup(const QString &name, Protocol protocol, KeyUsage usage) const { d->ensureCachePopulated(); Q_ASSERT(usage == KeyUsage::Sign || usage == KeyUsage::Encrypt); for (const auto &group : std::as_const(d->m_groups)) { if (group.name() == name) { const KeyGroup::Keys &keys = group.keys(); if (allKeysAllowUsage(keys, usage) && (protocol == UnknownProtocol || allKeysHaveProtocol(keys, protocol))) { return group; } } } return {}; } std::vector KeyCache::getGroupKeys(const QString &groupName) const { std::vector result; for (const KeyGroup &g : std::as_const(d->m_groups)) { if (g.name() == groupName) { const KeyGroup::Keys &keys = g.keys(); std::copy(keys.cbegin(), keys.cend(), std::back_inserter(result)); } } _detail::sort_by_fpr(result); _detail::remove_duplicates_by_fpr(result); return result; } void KeyCache::setKeys(const std::vector &keys) { // disable regular key listing and cancel running key listing setRefreshInterval(0); cancelKeyListing(); clear(); insert(keys); d->m_initalized = true; Q_EMIT keyListingDone(KeyListResult()); } void KeyCache::setGroups(const std::vector &groups) { Q_ASSERT(d->m_initalized && "Call setKeys() before setting groups"); d->m_groups = groups; Q_EMIT keysMayHaveChanged(); } #include "moc_keycache.cpp" #include "moc_keycache_p.cpp" diff --git a/src/ui/cryptoconfigmodule.cpp b/src/ui/cryptoconfigmodule.cpp index c63b03c8b..385c06a52 100644 --- a/src/ui/cryptoconfigmodule.cpp +++ b/src/ui/cryptoconfigmodule.cpp @@ -1,1042 +1,1041 @@ /* cryptoconfigmodule.cpp This file is part of kgpgcertmanager SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "cryptoconfigmodule.h" #include "cryptoconfigentryreaderport_p.h" #include "cryptoconfigmodule_p.h" #include "directoryserviceswidget.h" #include "filenamerequester.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 #include #include using namespace Kleo; namespace { class ScrollArea : public QScrollArea { public: explicit ScrollArea(QWidget *p) : QScrollArea(p) { } QSize sizeHint() const override { const QSize wsz = widget() ? widget()->sizeHint() : QSize(); return {wsz.width() + style()->pixelMetric(QStyle::PM_ScrollBarExtent), QScrollArea::sizeHint().height()}; } }; } inline QIcon loadIcon(const QString &s) { QString ss = s; const static QRegularExpression reg(QRegularExpression(QLatin1String("[^a-zA-Z0-9_]"))); return QIcon::fromTheme(ss.replace(reg, QStringLiteral("-"))); } static unsigned int num_components_with_options(const QGpgME::CryptoConfig *config) { if (!config) { return 0; } const QStringList components = config->componentList(); unsigned int result = 0; for (QStringList::const_iterator it = components.begin(); it != components.end(); ++it) { if (const QGpgME::CryptoConfigComponent *const comp = config->component(*it)) { if (!comp->groupList().empty()) { ++result; } } } return result; } static KPageView::FaceType determineJanusFace(const QGpgME::CryptoConfig *config, Kleo::CryptoConfigModule::Layout layout, bool &ok) { ok = true; if (num_components_with_options(config) < 2) { ok = false; return KPageView::Plain; } switch (layout) { case CryptoConfigModule::LinearizedLayout: return KPageView::Plain; case CryptoConfigModule::TabbedLayout: return KPageView::Tabbed; case CryptoConfigModule::IconListLayout: return KPageView::List; } Q_ASSERT(!"we should never get here"); return KPageView::List; } Kleo::CryptoConfigModule::CryptoConfigModule(QGpgME::CryptoConfig *config, QWidget *parent) : KPageWidget(parent) , mConfig(config) { init(IconListLayout); } Kleo::CryptoConfigModule::CryptoConfigModule(QGpgME::CryptoConfig *config, Layout layout, QWidget *parent) : KPageWidget(parent) , mConfig(config) { init(layout); } void Kleo::CryptoConfigModule::init(Layout layout) { if (QLayout *l = this->layout()) { l->setContentsMargins(0, 0, 0, 0); } QGpgME::CryptoConfig *const config = mConfig; bool configOK = false; const KPageView::FaceType type = determineJanusFace(config, layout, configOK); setFaceType(type); QVBoxLayout *vlay = nullptr; QWidget *vbox = nullptr; if (type == Plain) { QWidget *w = new QWidget(this); auto l = new QVBoxLayout(w); l->setContentsMargins(0, 0, 0, 0); auto s = new QScrollArea(w); s->setFrameStyle(QFrame::NoFrame); s->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); s->setWidgetResizable(true); l->addWidget(s); vbox = new QWidget(s->viewport()); vlay = new QVBoxLayout(vbox); vlay->setContentsMargins(0, 0, 0, 0); s->setWidget(vbox); addPage(w, configOK ? QString() : i18n("GpgConf Error")); } const QStringList components = sortComponentList(config->componentList()); for (QStringList::const_iterator it = components.begin(); it != components.end(); ++it) { // qCDebug(KLEO_UI_LOG) <<"Component" << (*it).toLocal8Bit() <<":"; QGpgME::CryptoConfigComponent *comp = config->component(*it); Q_ASSERT(comp); if (comp->groupList().empty()) { continue; } std::unique_ptr compGUI(new CryptoConfigComponentGUI(this, comp)); compGUI->setObjectName(*it); // KJanusWidget doesn't seem to have iterators, so we store a copy... mComponentGUIs.append(compGUI.get()); if (type == Plain) { QGroupBox *gb = new QGroupBox(comp->description(), vbox); (new QVBoxLayout(gb))->addWidget(compGUI.release()); vlay->addWidget(gb); } else { vbox = new QWidget(this); vlay = new QVBoxLayout(vbox); vlay->setContentsMargins(0, 0, 0, 0); KPageWidgetItem *pageItem = new KPageWidgetItem(vbox, comp->description()); if (type != Tabbed) { pageItem->setIcon(loadIcon(comp->iconName())); } addPage(pageItem); QScrollArea *scrollArea = type == Tabbed ? new QScrollArea(vbox) : new ScrollArea(vbox); scrollArea->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); scrollArea->setWidgetResizable(true); vlay->addWidget(scrollArea); const QSize compGUISize = compGUI->sizeHint(); scrollArea->setWidget(compGUI.release()); // Set a nice startup size const int deskHeight = screen()->size().height(); int dialogHeight; if (deskHeight > 1000) { // very big desktop ? dialogHeight = 800; } else if (deskHeight > 650) { // big desktop ? dialogHeight = 500; } else { // small (800x600, 640x480) desktop dialogHeight = 400; } Q_ASSERT(scrollArea->widget()); if (type != Tabbed) { scrollArea->setMinimumHeight(qMin(compGUISize.height(), dialogHeight)); } } } if (mComponentGUIs.empty()) { const QString msg = i18n( "The gpgconf tool used to provide the information " "for this dialog does not seem to be installed " "properly. It did not return any components. " "Try running \"%1\" on the command line for more " "information.", components.empty() ? QLatin1String("gpgconf --list-components") : QLatin1String("gpgconf --list-options gpg")); QLabel *label = new QLabel(msg, vbox); label->setWordWrap(true); label->setMinimumHeight(fontMetrics().lineSpacing() * 5); vlay->addWidget(label); } } namespace { template QStringList sortConfigEntries(const Iterator orderBegin, const Iterator orderEnd, const QStringList &entries) { // components sorting algorithm: // 1. components with predefined order (provided via orderBegin / orderEnd) // 2. other components sorted alphabetically QStringList result; QStringList others; for (auto it = orderBegin; it != orderEnd; ++it) { if (entries.contains(*it)) { result.append(*it); } } for (const auto &item : entries) { if (!result.contains(item)) { others.append(item); } } others.sort(); result.append(others); return result; } } // namespace QStringList Kleo::CryptoConfigModule::sortComponentList(const QStringList &components) { static const std::array order = { QStringLiteral("gpg"), QStringLiteral("gpgsm"), QStringLiteral("gpg-agent"), QStringLiteral("dirmngr"), QStringLiteral("pinentry"), QStringLiteral("scdaemon"), }; return sortConfigEntries(order.begin(), order.end(), components); } QStringList Kleo::CryptoConfigModule::sortGroupList(const QString &moduleName, const QStringList &groups) { if (moduleName == QStringLiteral("gpg")) { static const std::array order = { QStringLiteral("Keyserver"), QStringLiteral("Configuration"), QStringLiteral("Monitor"), QStringLiteral("Debug"), }; return sortConfigEntries(order.begin(), order.end(), groups); } else if (moduleName == QStringLiteral("gpgsm")) { static const std::array order = { QStringLiteral("Security"), QStringLiteral("Configuration"), QStringLiteral("Monitor"), QStringLiteral("Debug"), }; return sortConfigEntries(order.begin(), order.end(), groups); } else if (moduleName == QStringLiteral("gpg-agent")) { static const std::array order = { QStringLiteral("Security"), QStringLiteral("Passphrase policy"), QStringLiteral("Configuration"), QStringLiteral("Monitor"), QStringLiteral("Debug"), }; return sortConfigEntries(order.begin(), order.end(), groups); } else if (moduleName == QStringLiteral("dirmngr")) { static const std::array order = { QStringLiteral("Keyserver"), QStringLiteral("HTTP"), QStringLiteral("LDAP"), QStringLiteral("OCSP"), QStringLiteral("Tor"), QStringLiteral("Enforcement"), QStringLiteral("Configuration"), QStringLiteral("Format"), QStringLiteral("Monitor"), QStringLiteral("Debug"), }; return sortConfigEntries(order.begin(), order.end(), groups); } else if (moduleName == QStringLiteral("scdaemon")) { static const std::array order = { QStringLiteral("Monitor"), QStringLiteral("Configuration"), QStringLiteral("Security"), QStringLiteral("Debug"), }; return sortConfigEntries(order.begin(), order.end(), groups); } else { qCDebug(KLEO_UI_LOG) << "Configuration groups order is not defined for " << moduleName; QStringList result(groups); result.sort(); return result; } } bool Kleo::CryptoConfigModule::hasError() const { return mComponentGUIs.empty(); } void Kleo::CryptoConfigModule::save() { bool changed = false; QList::Iterator it = mComponentGUIs.begin(); for (; it != mComponentGUIs.end(); ++it) { if ((*it)->save()) { changed = true; } } if (changed) { mConfig->sync(true /*runtime*/); } } void Kleo::CryptoConfigModule::reset() { QList::Iterator it = mComponentGUIs.begin(); for (; it != mComponentGUIs.end(); ++it) { (*it)->load(); } } void Kleo::CryptoConfigModule::defaults() { QList::Iterator it = mComponentGUIs.begin(); for (; it != mComponentGUIs.end(); ++it) { (*it)->defaults(); } } void Kleo::CryptoConfigModule::cancel() { mConfig->clear(); } //// namespace { bool offerEntryForConfiguration(QGpgME::CryptoConfigEntry *entry) { static const QRegularExpression entryPathGroupSegmentRegexp{QStringLiteral("/.*/")}; static std::set entriesToExclude; if (entriesToExclude.empty()) { entriesToExclude.insert(QStringLiteral("gpg/keyserver")); if (engineIsVersion(2, 3, 5, GpgME::GpgConfEngine) || (engineIsVersion(2, 2, 34, GpgME::GpgConfEngine) && !engineIsVersion(2, 3, 0, GpgME::GpgConfEngine))) { // exclude for 2.2.{34,...} and 2.3.5+ entriesToExclude.insert(QStringLiteral("gpgsm/keyserver")); } } const bool de_vs = DeVSCompliance::isActive(); // Skip "dangerous" expert options if we are running in CO_DE_VS. // Otherwise, skip any options beyond "invisible" (== expert + 1) level. const auto maxEntryLevel = de_vs ? QGpgME::CryptoConfigEntry::Level_Advanced // : QGpgME::CryptoConfigEntry::Level_Expert + 1; // we ignore the group when looking up entries to exclude because entries // are uniquely identified by their name and their component const auto entryId = entry->path().replace(entryPathGroupSegmentRegexp, QLatin1String{"/"}).toLower(); return (entry->level() <= maxEntryLevel) && (entriesToExclude.find(entryId) == entriesToExclude.end()); } auto getGroupEntriesToOfferForConfiguration(QGpgME::CryptoConfigGroup *group) { std::vector result; const auto entryNames = group->entryList(); for (const auto &entryName : entryNames) { auto *const entry = group->entry(entryName); Q_ASSERT(entry); if (offerEntryForConfiguration(entry)) { result.push_back(entry); } else { qCDebug(KLEO_UI_LOG) << "entry" << entry->path() << "too advanced or excluded explicitly, skipping"; } } return result; } } Kleo::CryptoConfigComponentGUI::CryptoConfigComponentGUI(CryptoConfigModule *module, QGpgME::CryptoConfigComponent *component, QWidget *parent) : QWidget(parent) , mComponent(component) { auto glay = new QGridLayout(this); const QStringList groups = module->sortGroupList(mComponent->name(), mComponent->groupList()); if (groups.size() > 1) { glay->setColumnMinimumWidth(0, 30); for (QStringList::const_iterator it = groups.begin(), end = groups.end(); it != end; ++it) { QGpgME::CryptoConfigGroup *group = mComponent->group(*it); Q_ASSERT(group); if (!group) { continue; } auto groupEntries = getGroupEntriesToOfferForConfiguration(group); if (groupEntries.size() == 0) { // skip groups without entries to be offered in the UI continue; } const QString title = group->description(); auto hbox = new QHBoxLayout; hbox->addWidget(new QLabel{title.isEmpty() ? *it : title, this}); hbox->addWidget(new KSeparator{Qt::Horizontal, this}, 1); const int row = glay->rowCount(); glay->addLayout(hbox, row, 0, 1, 3); mGroupGUIs.append(new CryptoConfigGroupGUI(module, group, groupEntries, glay, this)); } } else if (!groups.empty()) { auto *const group = mComponent->group(groups.front()); auto groupEntries = getGroupEntriesToOfferForConfiguration(group); if (groupEntries.size() > 0) { mGroupGUIs.append(new CryptoConfigGroupGUI(module, group, groupEntries, glay, this)); } } glay->setRowStretch(glay->rowCount(), 1); } bool Kleo::CryptoConfigComponentGUI::save() { bool changed = false; QList::Iterator it = mGroupGUIs.begin(); for (; it != mGroupGUIs.end(); ++it) { if ((*it)->save()) { changed = true; } } return changed; } void Kleo::CryptoConfigComponentGUI::load() { QList::Iterator it = mGroupGUIs.begin(); for (; it != mGroupGUIs.end(); ++it) { (*it)->load(); } } void Kleo::CryptoConfigComponentGUI::defaults() { QList::Iterator it = mGroupGUIs.begin(); for (; it != mGroupGUIs.end(); ++it) { (*it)->defaults(); } } //// Kleo::CryptoConfigGroupGUI::CryptoConfigGroupGUI(CryptoConfigModule *module, QGpgME::CryptoConfigGroup *group, const std::vector &entries, QGridLayout *glay, QWidget *widget) : QObject(module) { const int startRow = glay->rowCount(); for (auto entry : entries) { CryptoConfigEntryGUI *entryGUI = CryptoConfigEntryGUIFactory::createEntryGUI(module, entry, entry->name(), glay, widget); if (entryGUI) { mEntryGUIs.append(entryGUI); entryGUI->load(); } } const int endRow = glay->rowCount() - 1; if (endRow < startRow) { return; } const QString iconName = group->iconName(); if (iconName.isEmpty()) { return; } QLabel *l = new QLabel(widget); l->setPixmap(loadIcon(iconName).pixmap(32, 32)); glay->addWidget(l, startRow, 0, endRow - startRow + 1, 1, Qt::AlignTop); } bool Kleo::CryptoConfigGroupGUI::save() { bool changed = false; QList::Iterator it = mEntryGUIs.begin(); for (; it != mEntryGUIs.end(); ++it) { if ((*it)->isChanged()) { (*it)->save(); changed = true; } } return changed; } void Kleo::CryptoConfigGroupGUI::load() { QList::Iterator it = mEntryGUIs.begin(); for (; it != mEntryGUIs.end(); ++it) { (*it)->load(); } } void Kleo::CryptoConfigGroupGUI::defaults() { QList::Iterator it = mEntryGUIs.begin(); for (; it != mEntryGUIs.end(); ++it) { (*it)->resetToDefault(); } } //// using constructor = CryptoConfigEntryGUI *(*)(CryptoConfigModule *, QGpgME::CryptoConfigEntry *, const QString &, QGridLayout *, QWidget *); namespace { template CryptoConfigEntryGUI *_create(CryptoConfigModule *m, QGpgME::CryptoConfigEntry *e, const QString &n, QGridLayout *l, QWidget *p) { return new T_Widget(m, e, n, l, p); } } static const struct WidgetsByEntryName { const char *entryGlob; constructor create; } widgetsByEntryName[] = { {"*/*/debug-level", &_create}, {"scdaemon/*/reader-port", &_create}, }; static const unsigned int numWidgetsByEntryName = sizeof widgetsByEntryName / sizeof *widgetsByEntryName; static const constructor listWidgets[QGpgME::CryptoConfigEntry::NumArgType] = { // None: A list of options with no arguments (e.g. -v -v -v) is shown as a spinbox &_create, nullptr, // String // Int/UInt: Let people type list of numbers (1,2,3....). Untested. &_create, &_create, nullptr, // Path nullptr, // Formerly URL &_create, nullptr, // DirPath }; static const constructor scalarWidgets[QGpgME::CryptoConfigEntry::NumArgType] = { // clang-format off &_create, // None &_create, // String &_create, // Int &_create, // UInt &_create, // Path nullptr, // Formerly URL nullptr, // LDAPURL &_create, // DirPath // clang-format on }; CryptoConfigEntryGUI *Kleo::CryptoConfigEntryGUIFactory::createEntryGUI(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) { Q_ASSERT(entry); // try to lookup by path: const QString path = entry->path(); for (unsigned int i = 0; i < numWidgetsByEntryName; ++i) { if (QRegExp(QLatin1String(widgetsByEntryName[i].entryGlob), Qt::CaseSensitive, QRegExp::Wildcard).exactMatch(path)) { return widgetsByEntryName[i].create(module, entry, entryName, glay, widget); } } // none found, so look up by type: const unsigned int argType = entry->argType(); Q_ASSERT(argType < QGpgME::CryptoConfigEntry::NumArgType); if (entry->isList()) { if (const constructor create = listWidgets[argType]) { return create(module, entry, entryName, glay, widget); } else { qCWarning(KLEO_UI_LOG) << "No widget implemented for list of type" << entry->argType(); } } else if (const constructor create = scalarWidgets[argType]) { return create(module, entry, entryName, glay, widget); } else { qCWarning(KLEO_UI_LOG) << "No widget implemented for type" << entry->argType(); } return nullptr; } //// Kleo::CryptoConfigEntryGUI::CryptoConfigEntryGUI(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName) : QObject(module) , mEntry(entry) , mName(entryName) , mChanged(false) { connect(this, &CryptoConfigEntryGUI::changed, module, &CryptoConfigModule::changed); } QString Kleo::CryptoConfigEntryGUI::description() const { QString descr = mEntry->description(); if (descr.isEmpty()) { // happens for expert options // String does not need to be translated because the options itself // are also not translated return QStringLiteral("\"%1\"").arg(mName); } if (i18nc("Translate this to 'yes' or 'no' (use the English words!) " "depending on whether your language uses " "Sentence style capitalization in GUI labels (yes) or not (no). " "Context: We get some backend strings in that have the wrong " "capitalization (in English, at least) so we need to force the " "first character to upper-case. It is this behaviour you can " "control for your language with this translation.", "yes") == QLatin1String("yes")) { descr[0] = descr[0].toUpper(); } return descr; } void Kleo::CryptoConfigEntryGUI::resetToDefault() { mEntry->resetToDefault(); load(); } //// Kleo::CryptoConfigEntryLineEdit::CryptoConfigEntryLineEdit(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) { const int row = glay->rowCount(); mLineEdit = new KLineEdit(widget); QLabel *label = new QLabel(description(), widget); label->setBuddy(mLineEdit); glay->addWidget(label, row, 1); glay->addWidget(mLineEdit, row, 2); if (entry->isReadOnly()) { label->setEnabled(false); mLineEdit->setEnabled(false); } else { connect(mLineEdit, &KLineEdit::textChanged, this, &CryptoConfigEntryLineEdit::slotChanged); } } void Kleo::CryptoConfigEntryLineEdit::doSave() { mEntry->setStringValue(mLineEdit->text()); } void Kleo::CryptoConfigEntryLineEdit::doLoad() { mLineEdit->setText(mEntry->stringValue()); } //// /* Note: Do not use "guru" as debug level but use the value 10. The former also enables the creation of hash dump files and thus leaves traces of plaintext on the disk. */ static const struct { const KLazyLocalizedString label; const char *name; } debugLevels[] = { {kli18n("0 - None"), "none"}, {kli18n("1 - Basic"), "basic"}, {kli18n("2 - Verbose"), "advanced"}, {kli18n("3 - More Verbose"), "expert"}, {kli18n("4 - All"), "10"}, }; static const unsigned int numDebugLevels = sizeof debugLevels / sizeof *debugLevels; Kleo::CryptoConfigEntryDebugLevel::CryptoConfigEntryDebugLevel(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) , mComboBox(new QComboBox(widget)) { QLabel *label = new QLabel(i18n("Set the debugging level to"), widget); label->setBuddy(mComboBox); for (unsigned int i = 0; i < numDebugLevels; ++i) { mComboBox->addItem(KLocalizedString(debugLevels[i].label).toString()); } if (entry->isReadOnly()) { label->setEnabled(false); mComboBox->setEnabled(false); } else { connect(mComboBox, qOverload(&QComboBox::currentIndexChanged), this, &CryptoConfigEntryDebugLevel::slotChanged); } const int row = glay->rowCount(); glay->addWidget(label, row, 1); glay->addWidget(mComboBox, row, 2); } void Kleo::CryptoConfigEntryDebugLevel::doSave() { const unsigned int idx = mComboBox->currentIndex(); if (idx < numDebugLevels) { mEntry->setStringValue(QLatin1String(debugLevels[idx].name)); } else { mEntry->setStringValue(QString()); } } void Kleo::CryptoConfigEntryDebugLevel::doLoad() { const QString str = mEntry->stringValue(); for (unsigned int i = 0; i < numDebugLevels; ++i) { if (str == QLatin1String(debugLevels[i].name)) { mComboBox->setCurrentIndex(i); return; } } mComboBox->setCurrentIndex(0); } //// Kleo::CryptoConfigEntryPath::CryptoConfigEntryPath(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) , mFileNameRequester(nullptr) { const int row = glay->rowCount(); mFileNameRequester = new FileNameRequester(widget); mFileNameRequester->setExistingOnly(false); mFileNameRequester->setFilter(QDir::Files); QLabel *label = new QLabel(description(), widget); label->setBuddy(mFileNameRequester); glay->addWidget(label, row, 1); glay->addWidget(mFileNameRequester, row, 2); if (entry->isReadOnly()) { label->setEnabled(false); mFileNameRequester->setEnabled(false); } else { connect(mFileNameRequester, &FileNameRequester::fileNameChanged, this, &CryptoConfigEntryPath::slotChanged); } } void Kleo::CryptoConfigEntryPath::doSave() { mEntry->setURLValue(QUrl::fromLocalFile(mFileNameRequester->fileName())); } void Kleo::CryptoConfigEntryPath::doLoad() { if (mEntry->urlValue().isLocalFile()) { mFileNameRequester->setFileName(mEntry->urlValue().toLocalFile()); } else { mFileNameRequester->setFileName(mEntry->urlValue().toString()); } } //// Kleo::CryptoConfigEntryDirPath::CryptoConfigEntryDirPath(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) , mFileNameRequester(nullptr) { const int row = glay->rowCount(); mFileNameRequester = new FileNameRequester(widget); mFileNameRequester->setExistingOnly(false); mFileNameRequester->setFilter(QDir::Dirs); QLabel *label = new QLabel(description(), widget); label->setBuddy(mFileNameRequester); glay->addWidget(label, row, 1); glay->addWidget(mFileNameRequester, row, 2); if (entry->isReadOnly()) { label->setEnabled(false); mFileNameRequester->setEnabled(false); } else { connect(mFileNameRequester, &FileNameRequester::fileNameChanged, this, &CryptoConfigEntryDirPath::slotChanged); } } void Kleo::CryptoConfigEntryDirPath::doSave() { mEntry->setURLValue(QUrl::fromLocalFile(mFileNameRequester->fileName())); } void Kleo::CryptoConfigEntryDirPath::doLoad() { mFileNameRequester->setFileName(mEntry->urlValue().toLocalFile()); } //// Kleo::CryptoConfigEntrySpinBox::CryptoConfigEntrySpinBox(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) { if (entry->argType() == QGpgME::CryptoConfigEntry::ArgType_None && entry->isList()) { mKind = ListOfNone; } else if (entry->argType() == QGpgME::CryptoConfigEntry::ArgType_UInt) { mKind = UInt; } else { Q_ASSERT(entry->argType() == QGpgME::CryptoConfigEntry::ArgType_Int); mKind = Int; } const int row = glay->rowCount(); mNumInput = new QSpinBox(widget); QLabel *label = new QLabel(description(), widget); label->setBuddy(mNumInput); glay->addWidget(label, row, 1); glay->addWidget(mNumInput, row, 2); if (entry->isReadOnly()) { label->setEnabled(false); mNumInput->setEnabled(false); } else { mNumInput->setMinimum(mKind == Int ? std::numeric_limits::min() : 0); mNumInput->setMaximum(std::numeric_limits::max()); connect(mNumInput, qOverload(&QSpinBox::valueChanged), this, &CryptoConfigEntrySpinBox::slotChanged); } } void Kleo::CryptoConfigEntrySpinBox::doSave() { int value = mNumInput->value(); switch (mKind) { case ListOfNone: mEntry->setNumberOfTimesSet(value); break; case UInt: mEntry->setUIntValue(value); break; case Int: mEntry->setIntValue(value); break; } } void Kleo::CryptoConfigEntrySpinBox::doLoad() { int value = 0; switch (mKind) { case ListOfNone: value = mEntry->numberOfTimesSet(); break; case UInt: value = mEntry->uintValue(); break; case Int: value = mEntry->intValue(); break; } mNumInput->setValue(value); } //// Kleo::CryptoConfigEntryCheckBox::CryptoConfigEntryCheckBox(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) { const int row = glay->rowCount(); mCheckBox = new QCheckBox(widget); glay->addWidget(mCheckBox, row, 1, 1, 2); mCheckBox->setText(description()); if (entry->isReadOnly()) { mCheckBox->setEnabled(false); } else { connect(mCheckBox, &QCheckBox::toggled, this, &CryptoConfigEntryCheckBox::slotChanged); } } void Kleo::CryptoConfigEntryCheckBox::doSave() { mEntry->setBoolValue(mCheckBox->isChecked()); } void Kleo::CryptoConfigEntryCheckBox::doLoad() { mCheckBox->setChecked(mEntry->boolValue()); } Kleo::CryptoConfigEntryLDAPURL::CryptoConfigEntryLDAPURL(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) { mLabel = new QLabel(widget); mPushButton = new QPushButton(entry->isReadOnly() ? i18n("Show...") : i18n("Edit..."), widget); const int row = glay->rowCount(); QLabel *label = new QLabel(description(), widget); label->setBuddy(mPushButton); glay->addWidget(label, row, 1); auto hlay = new QHBoxLayout; glay->addLayout(hlay, row, 2); hlay->addWidget(mLabel, 1); hlay->addWidget(mPushButton); if (entry->isReadOnly()) { mLabel->setEnabled(false); } connect(mPushButton, &QPushButton::clicked, this, &CryptoConfigEntryLDAPURL::slotOpenDialog); } void Kleo::CryptoConfigEntryLDAPURL::doLoad() { setURLList(mEntry->urlValueList()); } void Kleo::CryptoConfigEntryLDAPURL::doSave() { mEntry->setURLValueList(mURLList); } void prepareURLCfgDialog(QDialog *dialog, DirectoryServicesWidget *dirserv, bool readOnly) { QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok, dialog); if (!readOnly) { buttonBox->addButton(QDialogButtonBox::Cancel); buttonBox->addButton(QDialogButtonBox::RestoreDefaults); QPushButton *defaultsBtn = buttonBox->button(QDialogButtonBox::RestoreDefaults); QObject::connect(defaultsBtn, &QPushButton::clicked, dirserv, &DirectoryServicesWidget::clear); QObject::connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); } QObject::connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); auto layout = new QVBoxLayout; layout->addWidget(dirserv); layout->addWidget(buttonBox); dialog->setLayout(layout); } void Kleo::CryptoConfigEntryLDAPURL::slotOpenDialog() { if (!gpgme_check_version("1.16.0")) { KMessageBox::error(mPushButton->parentWidget(), i18n("Configuration of directory services is not possible " "because the used gpgme libraries are too old."), i18n("Sorry")); return; } // I'm a bad boy and I do it all on the stack. Enough classes already :) // This is just a simple dialog around the directory-services-widget QDialog dialog(mPushButton->parentWidget()); dialog.setWindowTitle(i18nc("@title:window", "Configure Directory Services")); auto dirserv = new DirectoryServicesWidget(&dialog); prepareURLCfgDialog(&dialog, dirserv, mEntry->isReadOnly()); dirserv->setReadOnly(mEntry->isReadOnly()); std::vector servers; std::transform(std::cbegin(mURLList), std::cend(mURLList), std::back_inserter(servers), [](const auto &url) { return KeyserverConfig::fromUrl(url); }); dirserv->setKeyservers(servers); if (dialog.exec()) { QList urls; const auto servers = dirserv->keyservers(); std::transform(std::begin(servers), std::end(servers), std::back_inserter(urls), [](const auto &server) { return server.toUrl(); }); setURLList(urls); slotChanged(); } } void Kleo::CryptoConfigEntryLDAPURL::setURLList(const QList &urlList) { mURLList = urlList; if (mURLList.isEmpty()) { mLabel->setText(i18n("None configured")); } else { mLabel->setText(i18np("1 server configured", "%1 servers configured", mURLList.count())); } } #include "moc_cryptoconfigmodule_p.cpp" diff --git a/src/ui/directoryserviceswidget.cpp b/src/ui/directoryserviceswidget.cpp index e456e6d40..cc3552071 100644 --- a/src/ui/directoryserviceswidget.cpp +++ b/src/ui/directoryserviceswidget.cpp @@ -1,412 +1,411 @@ /* ui/directoryserviceswidget.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2001, 2002, 2004 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2017 Bundesamnt für Sicherheit in der Informationstechnik SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "directoryserviceswidget.h" #include "editdirectoryservicedialog.h" #include #include #include #include -#include #include #include #include #include #include #include using namespace Kleo; namespace { bool activeDirectoryIsSupported() { return engineIsVersion(2, 2, 28, GpgME::GpgSMEngine); } bool isStandardActiveDirectory(const KeyserverConfig &keyserver) { return (keyserver.authentication() == KeyserverAuthentication::ActiveDirectory) && keyserver.host().isEmpty(); } bool keyserverIsEditable(const KeyserverConfig &keyserver) { // standard AD is not editable return !isStandardActiveDirectory(keyserver); } class KeyserverModel : public QAbstractListModel { Q_OBJECT public: explicit KeyserverModel(QObject *parent = nullptr) : QAbstractListModel{parent} { } void setKeyservers(const std::vector &servers) { clear(); beginInsertRows(QModelIndex(), 0, servers.size() - 1); m_items = servers; endInsertRows(); } void addKeyserver(const KeyserverConfig &keyserver) { const auto row = m_items.size(); beginInsertRows(QModelIndex(), row, row); m_items.push_back(keyserver); endInsertRows(); } KeyserverConfig getKeyserver(unsigned int id) { if (id >= m_items.size()) { qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id; return {}; } return m_items[id]; } void updateKeyserver(unsigned int id, const KeyserverConfig &keyserver) { if (id >= m_items.size()) { qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id; return; } m_items[id] = keyserver; Q_EMIT dataChanged(index(id), index(id)); } void deleteKeyserver(unsigned int id) { if (id >= m_items.size()) { qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id; return; } beginRemoveRows(QModelIndex(), id, id); m_items.erase(m_items.begin() + id); endRemoveRows(); } void clear() { if (m_items.empty()) { return; } beginRemoveRows(QModelIndex(), 0, m_items.size() - 1); m_items.clear(); endRemoveRows(); } int rowCount(const QModelIndex & = QModelIndex()) const override { return m_items.size(); } QVariant data(const QModelIndex &index, int role) const override { if (!index.isValid()) { return {}; } switch (role) { case Qt::DisplayRole: case Qt::EditRole: { const auto keyserver = m_items[index.row()]; return isStandardActiveDirectory(keyserver) ? i18n("Active Directory") : keyserver.host(); } } return {}; } bool hasActiveDirectory() { // check whether any of the model items represents an Active Directory keyserver return std::any_of(std::cbegin(m_items), std::cend(m_items), isStandardActiveDirectory); } private: using QAbstractListModel::setData; private: std::vector m_items; }; } class DirectoryServicesWidget::Private { DirectoryServicesWidget *const q; struct { QListView *keyserverList = nullptr; QToolButton *newButton = nullptr; QAction *addActiveDirectoryAction = nullptr; QAction *addLdapServerAction = nullptr; QPushButton *editButton = nullptr; QPushButton *deleteButton = nullptr; } ui; KeyserverModel *keyserverModel = nullptr; bool readOnly = false; public: Private(DirectoryServicesWidget *qq) : q(qq) { auto mainLayout = new QVBoxLayout{q}; auto gridLayout = new QGridLayout{}; gridLayout->setColumnStretch(0, 1); gridLayout->setRowStretch(1, 1); keyserverModel = new KeyserverModel{q}; ui.keyserverList = new QListView(); ui.keyserverList->setModel(keyserverModel); ui.keyserverList->setModelColumn(0); ui.keyserverList->setSelectionBehavior(QAbstractItemView::SelectRows); ui.keyserverList->setSelectionMode(QAbstractItemView::SingleSelection); ui.keyserverList->setWhatsThis(i18nc("@info:whatsthis", "This is a list of all directory services that are configured for use with X.509.")); gridLayout->addWidget(ui.keyserverList, 1, 0); auto groupsButtonLayout = new QVBoxLayout(); auto menu = new QMenu{q}; ui.addActiveDirectoryAction = menu->addAction(i18n("Active Directory"), [this]() { addActiveDirectory(); }); ui.addActiveDirectoryAction->setToolTip(i18nc("@info:tooltip", "Click to use a directory service running on your Active Directory. " "This works only on Windows and requires GnuPG 2.2.28 or later.")); ui.addActiveDirectoryAction->setEnabled(activeDirectoryIsSupported()); ui.addLdapServerAction = menu->addAction(i18n("LDAP Server"), [this]() { addLdapServer(); }); ui.addLdapServerAction->setToolTip(i18nc("@info:tooltip", "Click to add a directory service provided by an LDAP server.")); ui.newButton = new QToolButton{q}; ui.newButton->setText(i18n("Add")); ui.newButton->setToolTip(i18nc("@info:tooltip", "Click to add a directory service.")); ui.newButton->setWhatsThis(i18nc("@info:whatsthis", "Click this button to add a directory service to the list of services. " "The change will only take effect once you acknowledge the configuration dialog.")); ui.newButton->setToolButtonStyle(Qt::ToolButtonTextOnly); ui.newButton->setPopupMode(QToolButton::InstantPopup); ui.newButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); // expand horizontally like the QPushButtons ui.newButton->setMenu(menu); groupsButtonLayout->addWidget(ui.newButton); ui.editButton = new QPushButton(i18n("Edit")); ui.editButton->setToolTip(i18nc("@info:tooltip", "Click to edit the selected service.")); ui.editButton->setWhatsThis(i18nc("@info:whatsthis", "Click this button to edit the settings of the currently selected directory service. " "The changes will only take effect once you acknowledge the configuration dialog.")); ui.editButton->setEnabled(false); groupsButtonLayout->addWidget(ui.editButton); ui.deleteButton = new QPushButton(i18n("Delete")); ui.deleteButton->setToolTip(i18nc("@info:tooltip", "Click to remove the selected service.")); ui.deleteButton->setWhatsThis(i18nc("@info:whatsthis", "Click this button to remove the currently selected directory service. " "The change will only take effect once you acknowledge the configuration dialog.")); ui.deleteButton->setEnabled(false); groupsButtonLayout->addWidget(ui.deleteButton); groupsButtonLayout->addStretch(1); gridLayout->addLayout(groupsButtonLayout, 1, 1); mainLayout->addLayout(gridLayout, /*stretch=*/1); connect(keyserverModel, &QAbstractItemModel::dataChanged, q, [this]() { modelChanged(); }); connect(keyserverModel, &QAbstractItemModel::rowsInserted, q, [this]() { modelChanged(); }); connect(keyserverModel, &QAbstractItemModel::rowsRemoved, q, [this]() { modelChanged(); }); connect(ui.keyserverList->selectionModel(), &QItemSelectionModel::selectionChanged, q, [this]() { selectionChanged(); }); connect(ui.keyserverList, &QListView::doubleClicked, q, [this](const QModelIndex &index) { if (!readOnly) { editKeyserver(index); } }); connect(ui.editButton, &QPushButton::clicked, q, [this]() { editKeyserver(); }); connect(ui.deleteButton, &QPushButton::clicked, q, [this]() { deleteKeyserver(); }); } void setReadOnly(bool ro) { readOnly = ro; updateActions(); } void setKeyservers(const std::vector &servers) { keyserverModel->setKeyservers(servers); } std::vector keyservers() const { std::vector result; result.reserve(keyserverModel->rowCount()); for (int row = 0; row < keyserverModel->rowCount(); ++row) { result.push_back(keyserverModel->getKeyserver(row)); } return result; } void clear() { if (keyserverModel->rowCount() == 0) { return; } keyserverModel->clear(); } private: auto selectedIndex() { const auto indexes = ui.keyserverList->selectionModel()->selectedRows(); return indexes.empty() ? QModelIndex() : indexes[0]; } void modelChanged() { updateActions(); Q_EMIT q->changed(); } void selectionChanged() { updateActions(); } void updateActions() { const auto index = selectedIndex(); ui.newButton->setEnabled(!readOnly); ui.addActiveDirectoryAction->setEnabled(activeDirectoryIsSupported() && !keyserverModel->hasActiveDirectory()); ui.editButton->setEnabled(!readOnly && index.isValid() && keyserverIsEditable(keyserverModel->getKeyserver(index.row()))); ui.deleteButton->setEnabled(!readOnly && index.isValid()); } void handleEditKeyserverDialogResult(const int id, const EditDirectoryServiceDialog *dialog) { if (id >= 0) { keyserverModel->updateKeyserver(id, dialog->keyserver()); } else { keyserverModel->addKeyserver(dialog->keyserver()); } } void showEditKeyserverDialog(const int id, const KeyserverConfig &keyserver, const QString &windowTitle) { QPointer dialog{new EditDirectoryServiceDialog{q}}; dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowModality(Qt::WindowModal); dialog->setWindowTitle(windowTitle); dialog->setKeyserver(keyserver); connect(dialog, &QDialog::accepted, q, [dialog, id, this] { handleEditKeyserverDialogResult(id, dialog); }); dialog->show(); } void addActiveDirectory() { KeyserverConfig keyserver; keyserver.setAuthentication(KeyserverAuthentication::ActiveDirectory); keyserverModel->addKeyserver(keyserver); } void addLdapServer() { showEditKeyserverDialog(-1, {}, i18nc("@title:window", "LDAP Directory Service")); } void editKeyserver(const QModelIndex &index = {}) { const auto serverIndex = index.isValid() ? index : selectedIndex(); if (!serverIndex.isValid()) { qCDebug(KLEO_UI_LOG) << __func__ << "selection is empty"; return; } const auto id = serverIndex.row(); const KeyserverConfig keyserver = keyserverModel->getKeyserver(id); if (!keyserverIsEditable(keyserver)) { qCDebug(KLEO_UI_LOG) << __func__ << "selected keyserver (id:" << id << ") cannot be modified"; return; } showEditKeyserverDialog(id, keyserver, i18nc("@title:window", "LDAP Directory Service")); } void deleteKeyserver() { const QModelIndex serverIndex = selectedIndex(); if (!serverIndex.isValid()) { qCDebug(KLEO_UI_LOG) << __func__ << "selection is empty"; return; } keyserverModel->deleteKeyserver(serverIndex.row()); } }; DirectoryServicesWidget::DirectoryServicesWidget(QWidget *parent) : QWidget{parent} , d{std::make_unique(this)} { } DirectoryServicesWidget::~DirectoryServicesWidget() = default; void DirectoryServicesWidget::setKeyservers(const std::vector &servers) { d->setKeyservers(servers); } std::vector DirectoryServicesWidget::keyservers() const { return d->keyservers(); } void DirectoryServicesWidget::setReadOnly(bool readOnly) { d->setReadOnly(readOnly); } void DirectoryServicesWidget::clear() { d->clear(); } #include "directoryserviceswidget.moc" diff --git a/src/ui/editdirectoryservicedialog.cpp b/src/ui/editdirectoryservicedialog.cpp index 56d841067..f47ae76a6 100644 --- a/src/ui/editdirectoryservicedialog.cpp +++ b/src/ui/editdirectoryservicedialog.cpp @@ -1,409 +1,408 @@ /* ui/editdirectoryservicedialog.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "editdirectoryservicedialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include using namespace Kleo; namespace { int defaultPort(KeyserverConnection connection) { return connection == KeyserverConnection::TunnelThroughTLS ? 636 : 389; } } class EditDirectoryServiceDialog::Private { EditDirectoryServiceDialog *const q; struct Ui { QLineEdit *hostEdit = nullptr; QSpinBox *portSpinBox = nullptr; QCheckBox *useDefaultPortCheckBox = nullptr; QButtonGroup *authenticationGroup = nullptr; QLineEdit *userEdit = nullptr; KPasswordLineEdit *passwordEdit = nullptr; QButtonGroup *connectionGroup = nullptr; KCollapsibleGroupBox *advancedSettings = nullptr; QLineEdit *baseDnEdit = nullptr; QLineEdit *additionalFlagsEdit = nullptr; QDialogButtonBox *buttonBox = nullptr; Ui(QWidget *parent) : hostEdit{new QLineEdit{parent}} , portSpinBox{new QSpinBox{parent}} , useDefaultPortCheckBox{new QCheckBox{parent}} , authenticationGroup{new QButtonGroup{parent}} , userEdit{new QLineEdit{parent}} , passwordEdit{new KPasswordLineEdit{parent}} , connectionGroup{new QButtonGroup{parent}} , advancedSettings{new KCollapsibleGroupBox{parent}} , baseDnEdit{new QLineEdit{parent}} , additionalFlagsEdit{new QLineEdit{parent}} , buttonBox{new QDialogButtonBox{parent}} { #define SET_OBJECT_NAME(x) x->setObjectName(QStringLiteral(#x)); SET_OBJECT_NAME(hostEdit) SET_OBJECT_NAME(portSpinBox) SET_OBJECT_NAME(useDefaultPortCheckBox) SET_OBJECT_NAME(authenticationGroup) SET_OBJECT_NAME(userEdit) SET_OBJECT_NAME(passwordEdit) SET_OBJECT_NAME(connectionGroup) SET_OBJECT_NAME(advancedSettings) SET_OBJECT_NAME(baseDnEdit) SET_OBJECT_NAME(additionalFlagsEdit) SET_OBJECT_NAME(buttonBox) #undef SET_OBJECT_NAME auto mainLayout = new QVBoxLayout{parent}; auto serverWidget = new QWidget{parent}; { auto layout = new QGridLayout{serverWidget}; layout->setColumnStretch(2, 1); int row = 0; layout->addWidget(new QLabel{i18n("Host:")}, row, 0); hostEdit->setToolTip(i18nc("@info:tooltip", "Enter the name or IP address of the server hosting the directory service.")); hostEdit->setClearButtonEnabled(true); layout->addWidget(hostEdit, row, 1, 1, -1); ++row; layout->addWidget(new QLabel{i18n("Port:")}, row, 0); portSpinBox->setRange(1, USHRT_MAX); portSpinBox->setToolTip(i18nc("@info:tooltip", "(Optional, the default is fine in most cases) " "Pick the port number the directory service is listening on.")); layout->addWidget(portSpinBox, row, 1); useDefaultPortCheckBox->setText(i18n("Use default")); useDefaultPortCheckBox->setChecked(true); layout->addWidget(useDefaultPortCheckBox, row, 2); } mainLayout->addWidget(serverWidget); auto authenticationWidget = new QGroupBox{i18n("Authentication"), parent}; { auto layout = new QVBoxLayout{authenticationWidget}; { auto radioButton = new QRadioButton{i18n("Anonymous")}; radioButton->setToolTip(i18nc("@info:tooltip", "Use an anonymous LDAP server that does not require authentication.")); radioButton->setChecked(true); authenticationGroup->addButton(radioButton, static_cast(KeyserverAuthentication::Anonymous)); layout->addWidget(radioButton); } { auto radioButton = new QRadioButton{i18n("Authenticate via Active Directory")}; if (!engineIsVersion(2, 2, 28, GpgME::GpgSMEngine)) { radioButton->setText(i18n("Authenticate via Active Directory (requires GnuPG 2.2.28 or later)")); } radioButton->setToolTip( i18nc("@info:tooltip", "On Windows, authenticate to the LDAP server using the Active Directory with the current user.")); authenticationGroup->addButton(radioButton, static_cast(KeyserverAuthentication::ActiveDirectory)); layout->addWidget(radioButton); } { auto radioButton = new QRadioButton{i18n("Authenticate with user and password")}; radioButton->setToolTip(i18nc("@info:tooltip", "Authenticate to the LDAP server with your LDAP credentials.")); authenticationGroup->addButton(radioButton, static_cast(KeyserverAuthentication::Password)); layout->addWidget(radioButton); } auto credentialsWidget = new QWidget{parent}; { auto layout = new QGridLayout{credentialsWidget}; layout->setColumnStretch(1, 1); int row = 0; layout->addWidget(new QLabel{i18n("User:")}, row, 0); userEdit->setToolTip(i18nc("@info:tooltip", "Enter your LDAP user resp. Bind DN for authenticating to the LDAP server.")); userEdit->setClearButtonEnabled(true); layout->addWidget(userEdit, row, 1); ++row; layout->addWidget(new QLabel{i18n("Password:")}, row, 0); passwordEdit->setToolTip(xi18nc("@info:tooltip", "Enter your password for authenticating to the LDAP server." "The password will be saved in the clear " "in a configuration file in your home directory.")); passwordEdit->setClearButtonEnabled(true); layout->addWidget(passwordEdit, row, 1); } layout->addWidget(credentialsWidget); } mainLayout->addWidget(authenticationWidget); auto securityWidget = new QGroupBox{i18n("Connection Security"), parent}; if (!engineIsVersion(2, 2, 28, GpgME::GpgSMEngine)) { securityWidget->setTitle(i18n("Connection Security (requires GnuPG 2.2.28 or later)")); } { auto layout = new QVBoxLayout{securityWidget}; { auto radioButton = new QRadioButton{i18n("Use default connection (probably not TLS secured)")}; radioButton->setToolTip(i18nc("@info:tooltip", "Use GnuPG's default to connect to the LDAP server. " "By default, GnuPG 2.3 and earlier use a plain, not TLS secured connection. " "(Not recommended)")); radioButton->setChecked(true); connectionGroup->addButton(radioButton, static_cast(KeyserverConnection::Default)); layout->addWidget(radioButton); } { auto radioButton = new QRadioButton{i18n("Do not use a TLS secured connection")}; radioButton->setToolTip(i18nc("@info:tooltip", "Use a plain, not TLS secured connection to connect to the LDAP server. " "(Not recommended)")); connectionGroup->addButton(radioButton, static_cast(KeyserverConnection::Plain)); layout->addWidget(radioButton); } { auto radioButton = new QRadioButton{i18n("Use TLS secured connection")}; radioButton->setToolTip(i18nc("@info:tooltip", "Use a standard TLS secured connection (initiated with STARTTLS) " "to connect to the LDAP server. " "(Recommended)")); connectionGroup->addButton(radioButton, static_cast(KeyserverConnection::UseSTARTTLS)); layout->addWidget(radioButton); } { auto radioButton = new QRadioButton{i18n("Tunnel LDAP through a TLS connection")}; radioButton->setToolTip(i18nc("@info:tooltip", "Use a TLS secured connection through which the connection to the " "LDAP server is tunneled. " "(Not recommended)")); connectionGroup->addButton(radioButton, static_cast(KeyserverConnection::TunnelThroughTLS)); layout->addWidget(radioButton); } } mainLayout->addWidget(securityWidget); advancedSettings->setTitle(i18n("Advanced Settings")); { auto layout = new QGridLayout{advancedSettings}; layout->setColumnStretch(1, 1); int row = 0; layout->addWidget(new QLabel{i18n("Base DN:")}, row, 0); baseDnEdit->setToolTip(i18nc("@info:tooltip", "(Optional, can usually be left empty) " "Enter the base DN for this LDAP server to limit searches " "to only that subtree of the directory.")); baseDnEdit->setClearButtonEnabled(true); layout->addWidget(baseDnEdit, row, 1); ++row; layout->addWidget(new QLabel{i18n("Additional flags:")}, row, 0); additionalFlagsEdit->setToolTip(i18nc("@info:tooltip", "Here you can enter additional flags that are not yet (or no longer) " "supported by Kleopatra. For example, older versions of GnuPG use " "ldaps to request a TLS secured connection.")); additionalFlagsEdit->setClearButtonEnabled(true); layout->addWidget(additionalFlagsEdit, row, 1); } mainLayout->addWidget(advancedSettings); mainLayout->addStretch(1); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); KGuiItem::assign(okButton, KStandardGuiItem::ok()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel()); mainLayout->addWidget(buttonBox); }; } ui; QString host() const { return ui.hostEdit->text().trimmed(); } int port() const { return ui.useDefaultPortCheckBox->isChecked() ? -1 : ui.portSpinBox->value(); } KeyserverAuthentication authentication() const { return KeyserverAuthentication{ui.authenticationGroup->checkedId()}; } QString user() const { return ui.userEdit->text().trimmed(); } QString password() const { return ui.passwordEdit->password(); // not trimmed } KeyserverConnection connection() const { return KeyserverConnection{ui.connectionGroup->checkedId()}; } QString baseDn() const { return ui.baseDnEdit->text().trimmed(); } QStringList additionalFlags() const { return transformInPlace(ui.additionalFlagsEdit->text().split(QLatin1Char{','}, Qt::SkipEmptyParts), [](const auto &flag) { return flag.trimmed(); }); } bool inputIsAcceptable() const { const bool hostIsSet = !host().isEmpty(); const bool requiredCredentialsAreSet = authentication() != KeyserverAuthentication::Password || (!user().isEmpty() && !password().isEmpty()); return hostIsSet && requiredCredentialsAreSet; } void updateWidgets() { ui.portSpinBox->setEnabled(!ui.useDefaultPortCheckBox->isChecked()); if (ui.useDefaultPortCheckBox->isChecked()) { ui.portSpinBox->setValue(defaultPort(connection())); } ui.userEdit->setEnabled(authentication() == KeyserverAuthentication::Password); ui.passwordEdit->setEnabled(authentication() == KeyserverAuthentication::Password); ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(inputIsAcceptable()); } public: Private(EditDirectoryServiceDialog *q) : q{q} , ui{q} { connect(ui.hostEdit, &QLineEdit::textEdited, q, [this]() { updateWidgets(); }); connect(ui.useDefaultPortCheckBox, &QCheckBox::toggled, q, [this]() { updateWidgets(); }); connect(ui.authenticationGroup, &QButtonGroup::idToggled, q, [this]() { updateWidgets(); }); connect(ui.userEdit, &QLineEdit::textEdited, q, [this]() { updateWidgets(); }); connect(ui.passwordEdit, &KPasswordLineEdit::passwordChanged, q, [this]() { updateWidgets(); }); connect(ui.connectionGroup, &QButtonGroup::idToggled, q, [this]() { updateWidgets(); }); connect(ui.buttonBox, &QDialogButtonBox::accepted, q, &EditDirectoryServiceDialog::accept); connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &EditDirectoryServiceDialog::reject); updateWidgets(); restoreLayout(); } ~Private() { saveLayout(); } void setKeyserver(const KeyserverConfig &keyserver) { ui.hostEdit->setText(keyserver.host()); ui.useDefaultPortCheckBox->setChecked(keyserver.port() == -1); ui.portSpinBox->setValue(keyserver.port() == -1 ? defaultPort(keyserver.connection()) : keyserver.port()); ui.authenticationGroup->button(static_cast(keyserver.authentication()))->setChecked(true); ui.userEdit->setText(keyserver.user()); ui.passwordEdit->setPassword(keyserver.password()); ui.connectionGroup->button(static_cast(keyserver.connection()))->setChecked(true); ui.baseDnEdit->setText(keyserver.ldapBaseDn()); ui.additionalFlagsEdit->setText(keyserver.additionalFlags().join(QLatin1Char{','})); ui.advancedSettings->setExpanded(!keyserver.ldapBaseDn().isEmpty() || !keyserver.additionalFlags().empty()); updateWidgets(); } KeyserverConfig keyserver() const { KeyserverConfig keyserver; keyserver.setHost(host()); keyserver.setPort(port()); keyserver.setAuthentication(authentication()); keyserver.setUser(user()); keyserver.setPassword(password()); keyserver.setConnection(connection()); keyserver.setLdapBaseDn(baseDn()); keyserver.setAdditionalFlags(additionalFlags()); return keyserver; } private: void saveLayout() { KConfigGroup configGroup{KSharedConfig::openStateConfig(), "EditDirectoryServiceDialog"}; configGroup.writeEntry("Size", q->size()); configGroup.sync(); } void restoreLayout() { const KConfigGroup configGroup{KSharedConfig::openStateConfig(), "EditDirectoryServiceDialog"}; const auto size = configGroup.readEntry("Size", QSize{}); if (size.isValid()) { q->resize(size); } } }; EditDirectoryServiceDialog::EditDirectoryServiceDialog(QWidget *parent, Qt::WindowFlags f) : QDialog{parent, f} , d{std::make_unique(this)} { setWindowTitle(i18nc("@title:window", "Edit Directory Service")); } EditDirectoryServiceDialog::~EditDirectoryServiceDialog() = default; void EditDirectoryServiceDialog::setKeyserver(const KeyserverConfig &keyserver) { d->setKeyserver(keyserver); } KeyserverConfig EditDirectoryServiceDialog::keyserver() const { return d->keyserver(); } diff --git a/src/ui/keyapprovaldialog.cpp b/src/ui/keyapprovaldialog.cpp index 9cc175fa8..461ab1f4e 100644 --- a/src/ui/keyapprovaldialog.cpp +++ b/src/ui/keyapprovaldialog.cpp @@ -1,208 +1,206 @@ /* -*- c++ -*- keyapprovaldialog.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB Based on kpgpui.h SPDX-FileCopyrightText: 2001, 2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keyapprovaldialog.h" #include "keyrequester.h" #include #include #include -#include #include #include #include #include #include #include #include #include #include -#include #include static Kleo::EncryptionPreference cb2pref(int i) { switch (i) { default: case 0: return Kleo::UnknownPreference; case 1: return Kleo::NeverEncrypt; case 2: return Kleo::AlwaysEncrypt; case 3: return Kleo::AlwaysEncryptIfPossible; case 4: return Kleo::AlwaysAskForEncryption; case 5: return Kleo::AskWheneverPossible; } } static int pref2cb(Kleo::EncryptionPreference p) { switch (p) { default: return 0; case Kleo::NeverEncrypt: return 1; case Kleo::AlwaysEncrypt: return 2; case Kleo::AlwaysEncryptIfPossible: return 3; case Kleo::AlwaysAskForEncryption: return 4; case Kleo::AskWheneverPossible: return 5; } } static QStringList preferencesStrings() { return QStringList() << xi18n("none") // << i18n("Never Encrypt with This Key") // << i18n("Always Encrypt with This Key") // << i18n("Encrypt Whenever Encryption is Possible") // << i18n("Always Ask") // << i18n("Ask Whenever Encryption is Possible"); } class Q_DECL_HIDDEN Kleo::KeyApprovalDialog::KeyApprovalDialogPrivate { public: KeyApprovalDialogPrivate() { } Kleo::KeyRequester *selfRequester = nullptr; QStringList addresses; std::vector requesters; std::vector preferences; bool prefsChanged = false; }; Kleo::KeyApprovalDialog::KeyApprovalDialog(const std::vector &recipients, const std::vector &sender, QWidget *parent) : QDialog(parent) , d(new KeyApprovalDialogPrivate()) { setWindowTitle(i18nc("@title:window", "Encryption Key Approval")); auto mainLayout = new QVBoxLayout(this); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &KeyApprovalDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &KeyApprovalDialog::reject); okButton->setDefault(true); Q_ASSERT(!recipients.empty()); QFrame *page = new QFrame(this); mainLayout->addWidget(page); mainLayout->addWidget(buttonBox); auto vlay = new QVBoxLayout(page); vlay->setContentsMargins(0, 0, 0, 0); vlay->addWidget(new QLabel(i18n("The following keys will be used for encryption:"), page)); auto sv = new QScrollArea(page); sv->setWidgetResizable(true); vlay->addWidget(sv); QWidget *view = new QWidget(sv->viewport()); auto glay = new QGridLayout(view); glay->setColumnStretch(1, 1); sv->setWidget(view); int row = -1; if (!sender.empty()) { ++row; glay->addWidget(new QLabel(i18n("Your keys:"), view), row, 0); d->selfRequester = new EncryptionKeyRequester(true, EncryptionKeyRequester::AllProtocols, view); d->selfRequester->setKeys(sender); glay->addWidget(d->selfRequester, row, 1); ++row; glay->addWidget(new KSeparator(Qt::Horizontal, view), row, 0, 1, 2); } const QStringList prefs = preferencesStrings(); for (auto it = recipients.begin(); it != recipients.end(); ++it) { ++row; glay->addWidget(new QLabel(i18n("Recipient:"), view), row, 0); glay->addWidget(new QLabel(it->address, view), row, 1); d->addresses.push_back(it->address); ++row; glay->addWidget(new QLabel(i18n("Encryption keys:"), view), row, 0); KeyRequester *req = new EncryptionKeyRequester(true, EncryptionKeyRequester::AllProtocols, view); req->setKeys(it->keys); glay->addWidget(req, row, 1); d->requesters.push_back(req); ++row; glay->addWidget(new QLabel(i18n("Encryption preference:"), view), row, 0); auto cb = new QComboBox(view); cb->setEditable(false); cb->addItems(prefs); glay->addWidget(cb, row, 1); cb->setCurrentIndex(pref2cb(it->pref)); connect(cb, qOverload(&QComboBox::activated), this, &KeyApprovalDialog::slotPrefsChanged); d->preferences.push_back(cb); } QSize size = sizeHint(); // don't make the dialog too large const QSize desk = screen()->size(); resize(QSize(qMin(size.width(), 3 * desk.width() / 4), qMin(size.height(), 7 * desk.height() / 8))); } Kleo::KeyApprovalDialog::~KeyApprovalDialog() = default; std::vector Kleo::KeyApprovalDialog::senderKeys() const { return d->selfRequester ? d->selfRequester->keys() : std::vector(); } std::vector Kleo::KeyApprovalDialog::items() const { Q_ASSERT(d->requesters.size() == static_cast(d->addresses.size())); Q_ASSERT(d->requesters.size() == d->preferences.size()); std::vector result; result.reserve(d->requesters.size()); QStringList::const_iterator ait = d->addresses.constBegin(); auto rit = d->requesters.begin(); auto cit = d->preferences.begin(); while (ait != d->addresses.constEnd()) { result.push_back(Item(*ait++, (*rit++)->keys(), cb2pref((*cit++)->currentIndex()))); } return result; } bool Kleo::KeyApprovalDialog::preferencesChanged() const { return d->prefsChanged; } void Kleo::KeyApprovalDialog::slotPrefsChanged() { d->prefsChanged = true; } diff --git a/src/ui/keyselectiondialog.cpp b/src/ui/keyselectiondialog.cpp index 1f18d42cd..f94dfa871 100644 --- a/src/ui/keyselectiondialog.cpp +++ b/src/ui/keyselectiondialog.cpp @@ -1,1012 +1,1010 @@ /* -*- c++ -*- keyselectiondialog.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB Based on kpgpui.cpp SPDX-FileCopyrightText: 2001, 2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keyselectiondialog.h" #include "keylistview.h" #include "progressdialog.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 using namespace Kleo; static bool checkKeyUsage(const GpgME::Key &key, unsigned int keyUsage, QString *statusString = nullptr) { auto setStatusString = [statusString](const QString &status) { if (statusString) { *statusString = status; } }; if (keyUsage & KeySelectionDialog::ValidKeys) { if (key.isInvalid()) { if (key.keyListMode() & GpgME::Validate) { qCDebug(KLEO_UI_LOG) << "key is invalid"; setStatusString(i18n("The key is not valid.")); return false; } else { qCDebug(KLEO_UI_LOG) << "key is invalid - ignoring"; } } if (key.isExpired()) { qCDebug(KLEO_UI_LOG) << "key is expired"; setStatusString(i18n("The key is expired.")); return false; } else if (key.isRevoked()) { qCDebug(KLEO_UI_LOG) << "key is revoked"; setStatusString(i18n("The key is revoked.")); return false; } else if (key.isDisabled()) { qCDebug(KLEO_UI_LOG) << "key is disabled"; setStatusString(i18n("The key is disabled.")); return false; } } if (keyUsage & KeySelectionDialog::EncryptionKeys && !key.canEncrypt()) { qCDebug(KLEO_UI_LOG) << "key can't encrypt"; setStatusString(i18n("The key is not designated for encryption.")); return false; } if (keyUsage & KeySelectionDialog::SigningKeys && !key.canSign()) { qCDebug(KLEO_UI_LOG) << "key can't sign"; setStatusString(i18n("The key is not designated for signing.")); return false; } if (keyUsage & KeySelectionDialog::CertificationKeys && !key.canCertify()) { qCDebug(KLEO_UI_LOG) << "key can't certify"; setStatusString(i18n("The key is not designated for certifying.")); return false; } if (keyUsage & KeySelectionDialog::AuthenticationKeys && !key.canAuthenticate()) { qCDebug(KLEO_UI_LOG) << "key can't authenticate"; setStatusString(i18n("The key is not designated for authentication.")); return false; } if (keyUsage & KeySelectionDialog::SecretKeys && !(keyUsage & KeySelectionDialog::PublicKeys) && !key.hasSecret()) { qCDebug(KLEO_UI_LOG) << "key isn't secret"; setStatusString(i18n("The key is not secret.")); return false; } if (keyUsage & KeySelectionDialog::TrustedKeys && key.protocol() == GpgME::OpenPGP && // only check this for secret keys for now. // Seems validity isn't checked for secret keylistings... !key.hasSecret()) { std::vector uids = key.userIDs(); for (std::vector::const_iterator it = uids.begin(); it != uids.end(); ++it) { if (!it->isRevoked() && it->validity() >= GpgME::UserID::Marginal) { return true; } } qCDebug(KLEO_UI_LOG) << "key has no UIDs with validity >= Marginal"; setStatusString(i18n("The key is not trusted enough.")); return false; } // X.509 keys are always trusted, else they won't be the keybox. // PENDING(marc) check that this ^ is correct setStatusString(i18n("The key can be used.")); return true; } static bool checkKeyUsage(const std::vector &keys, unsigned int keyUsage) { for (auto it = keys.begin(); it != keys.end(); ++it) { if (!checkKeyUsage(*it, keyUsage)) { return false; } } return true; } namespace { class ColumnStrategy : public KeyListView::ColumnStrategy { public: ColumnStrategy(unsigned int keyUsage); QString title(int col) const override; int width(int col, const QFontMetrics &fm) const override; QString text(const GpgME::Key &key, int col) const override; QString accessibleText(const GpgME::Key &key, int column) const override; QString toolTip(const GpgME::Key &key, int col) const override; QIcon icon(const GpgME::Key &key, int col) const override; private: const QIcon mKeyGoodPix, mKeyBadPix, mKeyUnknownPix, mKeyValidPix; const unsigned int mKeyUsage; }; static QString iconPath(const QString &name) { return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("libkleopatra/pics/") + name + QStringLiteral(".png")); } ColumnStrategy::ColumnStrategy(unsigned int keyUsage) : KeyListView::ColumnStrategy() , mKeyGoodPix(iconPath(QStringLiteral("key_ok"))) , mKeyBadPix(iconPath(QStringLiteral("key_bad"))) , mKeyUnknownPix(iconPath(QStringLiteral("key_unknown"))) , mKeyValidPix(iconPath(QStringLiteral("key"))) , mKeyUsage(keyUsage) { if (keyUsage == 0) { qCWarning(KLEO_UI_LOG) << "KeySelectionDialog: keyUsage == 0. You want to use AllKeys instead."; } } QString ColumnStrategy::title(int col) const { switch (col) { case 0: return i18n("Key ID"); case 1: return i18n("User ID"); default: return QString(); } } int ColumnStrategy::width(int col, const QFontMetrics &fm) const { if (col == 0) { static const char hexchars[] = "0123456789ABCDEF"; int maxWidth = 0; for (unsigned int i = 0; i < 16; ++i) { maxWidth = qMax(fm.boundingRect(QLatin1Char(hexchars[i])).width(), maxWidth); } return 8 * maxWidth + 2 * 16 /* KIconLoader::SizeSmall */; } return KeyListView::ColumnStrategy::width(col, fm); } QString ColumnStrategy::text(const GpgME::Key &key, int col) const { switch (col) { case 0: { if (key.shortKeyID()) { return Formatting::prettyID(key.shortKeyID()); } else { return xi18n("unknown"); } } case 1: { const char *uid = key.userID(0).id(); if (key.protocol() == GpgME::OpenPGP) { return uid && *uid ? QString::fromUtf8(uid) : QString(); } else { // CMS return DN(uid).prettyDN(); } } default: return QString(); } } QString ColumnStrategy::accessibleText(const GpgME::Key &key, int col) const { switch (col) { case 0: { if (key.shortKeyID()) { return Formatting::accessibleHexID(key.shortKeyID()); } [[fallthrough]]; } default: return {}; } } QString ColumnStrategy::toolTip(const GpgME::Key &key, int) const { const char *uid = key.userID(0).id(); const char *fpr = key.primaryFingerprint(); const char *issuer = key.issuerName(); const GpgME::Subkey subkey = key.subkey(0); const QString expiry = Formatting::expirationDateString(subkey); const QString creation = Formatting::creationDateString(subkey); QString keyStatusString; if (!checkKeyUsage(key, mKeyUsage, &keyStatusString)) { // Show the status in bold if there is a problem keyStatusString = QLatin1String("") % keyStatusString % QLatin1String(""); } QString html = QStringLiteral("

"); if (key.protocol() == GpgME::OpenPGP) { html += i18n("OpenPGP key for %1", uid ? QString::fromUtf8(uid) : i18n("unknown")); } else { html += i18n("S/MIME key for %1", uid ? DN(uid).prettyDN() : i18n("unknown")); } html += QStringLiteral("

"); const auto addRow = [&html](const QString &name, const QString &value) { html += QStringLiteral("").arg(name, value); }; addRow(i18n("Valid from"), creation); addRow(i18n("Valid until"), expiry); addRow(i18nc("Key fingerprint", "Fingerprint"), fpr ? QString::fromLatin1(fpr) : i18n("unknown")); if (key.protocol() != GpgME::OpenPGP) { addRow(i18nc("Key issuer", "Issuer"), issuer ? DN(issuer).prettyDN() : i18n("unknown")); } addRow(i18nc("Key status", "Status"), keyStatusString); html += QStringLiteral("
%1: %2
"); return html; } QIcon ColumnStrategy::icon(const GpgME::Key &key, int col) const { if (col != 0) { return QIcon(); } // this key did not undergo a validating keylisting yet: if (!(key.keyListMode() & GpgME::Validate)) { return mKeyUnknownPix; } if (!checkKeyUsage(key, mKeyUsage)) { return mKeyBadPix; } if (key.protocol() == GpgME::CMS) { return mKeyGoodPix; } switch (key.userID(0).validity()) { default: case GpgME::UserID::Unknown: case GpgME::UserID::Undefined: return mKeyUnknownPix; case GpgME::UserID::Never: return mKeyValidPix; case GpgME::UserID::Marginal: case GpgME::UserID::Full: case GpgME::UserID::Ultimate: return mKeyGoodPix; } } } static const int sCheckSelectionDelay = 250; KeySelectionDialog::KeySelectionDialog(QWidget *parent, Options options) : QDialog(parent) , mOpenPGPBackend(QGpgME::openpgp()) , mSMIMEBackend(QGpgME::smime()) , mKeyUsage(AllKeys) { qCDebug(KLEO_UI_LOG) << "mTruncated:" << mTruncated << "mSavedOffsetY:" << mSavedOffsetY; setUpUI(options, QString()); } KeySelectionDialog::KeySelectionDialog(const QString &title, const QString &text, const std::vector &selectedKeys, unsigned int keyUsage, bool extendedSelection, bool rememberChoice, QWidget *parent, bool modal) : QDialog(parent) , mSelectedKeys(selectedKeys) , mKeyUsage(keyUsage) { setWindowTitle(title); setModal(modal); init(rememberChoice, extendedSelection, text, QString()); } KeySelectionDialog::KeySelectionDialog(const QString &title, const QString &text, const QString &initialQuery, const std::vector &selectedKeys, unsigned int keyUsage, bool extendedSelection, bool rememberChoice, QWidget *parent, bool modal) : QDialog(parent) , mSelectedKeys(selectedKeys) , mKeyUsage(keyUsage) , mSearchText(initialQuery) , mInitialQuery(initialQuery) { setWindowTitle(title); setModal(modal); init(rememberChoice, extendedSelection, text, initialQuery); } KeySelectionDialog::KeySelectionDialog(const QString &title, const QString &text, const QString &initialQuery, unsigned int keyUsage, bool extendedSelection, bool rememberChoice, QWidget *parent, bool modal) : QDialog(parent) , mKeyUsage(keyUsage) , mSearchText(initialQuery) , mInitialQuery(initialQuery) { setWindowTitle(title); setModal(modal); init(rememberChoice, extendedSelection, text, initialQuery); } void KeySelectionDialog::setUpUI(Options options, const QString &initialQuery) { auto mainLayout = new QVBoxLayout(this); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); mOkButton = buttonBox->button(QDialogButtonBox::Ok); mOkButton->setDefault(true); mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return); mCheckSelectionTimer = new QTimer(this); mStartSearchTimer = new QTimer(this); QFrame *page = new QFrame(this); mainLayout->addWidget(page); mainLayout->addWidget(buttonBox); mTopLayout = new QVBoxLayout(page); mTopLayout->setContentsMargins(0, 0, 0, 0); mTextLabel = new QLabel(page); mTextLabel->setWordWrap(true); // Setting the size policy is necessary as a workaround for https://issues.kolab.org/issue4429 // and http://bugreports.qt.nokia.com/browse/QTBUG-8740 mTextLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); connect(mTextLabel, &QLabel::linkActivated, this, &KeySelectionDialog::slotStartCertificateManager); mTopLayout->addWidget(mTextLabel); mTextLabel->hide(); QPushButton *const searchExternalPB = new QPushButton(i18n("Search for &External Certificates"), page); mTopLayout->addWidget(searchExternalPB, 0, Qt::AlignLeft); connect(searchExternalPB, &QAbstractButton::clicked, this, &KeySelectionDialog::slotStartSearchForExternalCertificates); if (initialQuery.isEmpty()) { searchExternalPB->hide(); } auto hlay = new QHBoxLayout(); mTopLayout->addLayout(hlay); auto le = new QLineEdit(page); le->setClearButtonEnabled(true); le->setText(initialQuery); QLabel *lbSearchFor = new QLabel(i18n("&Search for:"), page); lbSearchFor->setBuddy(le); hlay->addWidget(lbSearchFor); hlay->addWidget(le, 1); le->setFocus(); connect(le, &QLineEdit::textChanged, this, [this](const QString &s) { slotSearch(s); }); connect(mStartSearchTimer, &QTimer::timeout, this, &KeySelectionDialog::slotFilter); mKeyListView = new KeyListView(new ColumnStrategy(mKeyUsage), nullptr, page); mKeyListView->setObjectName(QStringLiteral("mKeyListView")); mKeyListView->header()->stretchLastSection(); mKeyListView->setRootIsDecorated(true); mKeyListView->setSortingEnabled(true); mKeyListView->header()->setSortIndicatorShown(true); mKeyListView->header()->setSortIndicator(1, Qt::AscendingOrder); // sort by User ID if (options & ExtendedSelection) { mKeyListView->setSelectionMode(QAbstractItemView::ExtendedSelection); } mTopLayout->addWidget(mKeyListView, 10); if (options & RememberChoice) { mRememberCB = new QCheckBox(i18n("&Remember choice"), page); mTopLayout->addWidget(mRememberCB); mRememberCB->setWhatsThis( i18n("

If you check this box your choice will " "be stored and you will not be asked again." "

")); } connect(mCheckSelectionTimer, &QTimer::timeout, this, [this]() { slotCheckSelection(); }); connectSignals(); connect(mKeyListView, &KeyListView::doubleClicked, this, &KeySelectionDialog::slotTryOk); connect(mKeyListView, &KeyListView::contextMenu, this, &KeySelectionDialog::slotRMB); if (options & RereadKeys) { QPushButton *button = new QPushButton(i18n("&Reread Keys")); buttonBox->addButton(button, QDialogButtonBox::ActionRole); connect(button, &QPushButton::clicked, this, &KeySelectionDialog::slotRereadKeys); } if (options & ExternalCertificateManager) { QPushButton *button = new QPushButton(i18n("&Start Certificate Manager")); buttonBox->addButton(button, QDialogButtonBox::ActionRole); connect(button, &QPushButton::clicked, this, [this]() { slotStartCertificateManager(); }); } connect(mOkButton, &QPushButton::clicked, this, &KeySelectionDialog::slotOk); connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &KeySelectionDialog::slotCancel); mTopLayout->activate(); if (qApp) { QSize dialogSize(sizeHint()); KConfigGroup dialogConfig(KSharedConfig::openStateConfig(), "Key Selection Dialog"); dialogSize = dialogConfig.readEntry("Dialog size", dialogSize); const QByteArray headerState = dialogConfig.readEntry("header", QByteArray()); if (!headerState.isEmpty()) { mKeyListView->header()->restoreState(headerState); } resize(dialogSize); } } void KeySelectionDialog::init(bool rememberChoice, bool extendedSelection, const QString &text, const QString &initialQuery) { Options options = {RereadKeys, ExternalCertificateManager}; options.setFlag(ExtendedSelection, extendedSelection); options.setFlag(RememberChoice, rememberChoice); setUpUI(options, initialQuery); setText(text); if (mKeyUsage & OpenPGPKeys) { mOpenPGPBackend = QGpgME::openpgp(); } if (mKeyUsage & SMIMEKeys) { mSMIMEBackend = QGpgME::smime(); } slotRereadKeys(); } KeySelectionDialog::~KeySelectionDialog() { disconnectSignals(); KConfigGroup dialogConfig(KSharedConfig::openStateConfig(), "Key Selection Dialog"); dialogConfig.writeEntry("Dialog size", size()); dialogConfig.writeEntry("header", mKeyListView->header()->saveState()); dialogConfig.sync(); } void KeySelectionDialog::setText(const QString &text) { mTextLabel->setText(text); mTextLabel->setVisible(!text.isEmpty()); } void KeySelectionDialog::setKeys(const std::vector &keys) { for (const GpgME::Key &key : keys) { mKeyListView->slotAddKey(key); } } void KeySelectionDialog::connectSignals() { if (mKeyListView->isMultiSelection()) { connect(mKeyListView, &QTreeWidget::itemSelectionChanged, this, &KeySelectionDialog::slotSelectionChanged); } else { connect(mKeyListView, qOverload(&KeyListView::selectionChanged), this, qOverload(&KeySelectionDialog::slotCheckSelection)); } } void KeySelectionDialog::disconnectSignals() { if (mKeyListView->isMultiSelection()) { disconnect(mKeyListView, &QTreeWidget::itemSelectionChanged, this, &KeySelectionDialog::slotSelectionChanged); } else { disconnect(mKeyListView, qOverload(&KeyListView::selectionChanged), this, qOverload(&KeySelectionDialog::slotCheckSelection)); } } const GpgME::Key &KeySelectionDialog::selectedKey() const { static const GpgME::Key null = GpgME::Key::null; if (mKeyListView->isMultiSelection() || !mKeyListView->selectedItem()) { return null; } return mKeyListView->selectedItem()->key(); } QString KeySelectionDialog::fingerprint() const { return QLatin1String(selectedKey().primaryFingerprint()); } QStringList KeySelectionDialog::fingerprints() const { QStringList result; for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) { if (const char *fpr = it->primaryFingerprint()) { result.push_back(QLatin1String(fpr)); } } return result; } QStringList KeySelectionDialog::pgpKeyFingerprints() const { QStringList result; for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) { if (it->protocol() == GpgME::OpenPGP) { if (const char *fpr = it->primaryFingerprint()) { result.push_back(QLatin1String(fpr)); } } } return result; } QStringList KeySelectionDialog::smimeFingerprints() const { QStringList result; for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) { if (it->protocol() == GpgME::CMS) { if (const char *fpr = it->primaryFingerprint()) { result.push_back(QLatin1String(fpr)); } } } return result; } void KeySelectionDialog::slotRereadKeys() { mKeyListView->clear(); mListJobCount = 0; mTruncated = 0; mSavedOffsetY = mKeyListView->verticalScrollBar()->value(); disconnectSignals(); mKeyListView->setEnabled(false); // FIXME: save current selection if (mOpenPGPBackend) { startKeyListJobForBackend(mOpenPGPBackend, std::vector(), false /*non-validating*/); } if (mSMIMEBackend) { startKeyListJobForBackend(mSMIMEBackend, std::vector(), false /*non-validating*/); } if (mListJobCount == 0) { mKeyListView->setEnabled(true); KMessageBox::information(this, i18n("No backends found for listing keys. " "Check your installation."), i18n("Key Listing Failed")); connectSignals(); } } void KeySelectionDialog::slotStartCertificateManager(const QString &query) { QStringList args; if (!query.isEmpty()) { args << QStringLiteral("--search") << query; } const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra")); if (exec.isEmpty()) { qCWarning(KLEO_UI_LOG) << "Could not find kleopatra executable in PATH"; KMessageBox::error(this, i18n("Could not start certificate manager; " "please check your installation."), i18n("Certificate Manager Error")); } else { QProcess::startDetached(QStringLiteral("kleopatra"), args); qCDebug(KLEO_UI_LOG) << "\nslotStartCertManager(): certificate manager started."; } } #ifndef __KLEO_UI_SHOW_KEY_LIST_ERROR_H__ #define __KLEO_UI_SHOW_KEY_LIST_ERROR_H__ static void showKeyListError(QWidget *parent, const GpgME::Error &err) { Q_ASSERT(err); const QString msg = i18n( "

An error occurred while fetching " "the keys from the backend:

" "

%1

", QString::fromLocal8Bit(err.asString())); KMessageBox::error(parent, msg, i18n("Key Listing Failed")); } #endif // __KLEO_UI_SHOW_KEY_LIST_ERROR_H__ namespace { struct ExtractFingerprint { QString operator()(const GpgME::Key &key) { return QLatin1String(key.primaryFingerprint()); } }; } void KeySelectionDialog::startKeyListJobForBackend(const QGpgME::Protocol *backend, const std::vector &keys, bool validate) { Q_ASSERT(backend); QGpgME::KeyListJob *job = backend->keyListJob(false, false, validate); // local, w/o sigs, validation as given if (!job) { return; } connect(job, &QGpgME::KeyListJob::result, this, &KeySelectionDialog::slotKeyListResult); if (validate) { connect(job, &QGpgME::KeyListJob::nextKey, mKeyListView, &KeyListView::slotRefreshKey); } else { connect(job, &QGpgME::KeyListJob::nextKey, mKeyListView, &KeyListView::slotAddKey); } QStringList fprs; std::transform(keys.begin(), keys.end(), std::back_inserter(fprs), ExtractFingerprint()); const GpgME::Error err = job->start(fprs, mKeyUsage & SecretKeys && !(mKeyUsage & PublicKeys)); if (err) { return showKeyListError(this, err); } #ifndef LIBKLEO_NO_PROGRESSDIALOG // FIXME: create a MultiProgressDialog: (void)new ProgressDialog(job, validate ? i18n("Checking selected keys...") : i18n("Fetching keys..."), this); #endif ++mListJobCount; } static void selectKeys(KeyListView *klv, const std::vector &selectedKeys) { klv->clearSelection(); if (selectedKeys.empty()) { return; } for (auto it = selectedKeys.begin(); it != selectedKeys.end(); ++it) { if (KeyListViewItem *item = klv->itemByFingerprint(it->primaryFingerprint())) { item->setSelected(true); } } } void KeySelectionDialog::slotKeyListResult(const GpgME::KeyListResult &res) { if (res.error()) { showKeyListError(this, res.error()); } else if (res.isTruncated()) { ++mTruncated; } if (--mListJobCount > 0) { return; // not yet finished... } if (mTruncated > 0) { KMessageBox::information(this, i18np("One backend returned truncated output.

" "Not all available keys are shown

", "%1 backends returned truncated output.

" "Not all available keys are shown

", mTruncated), i18n("Key List Result")); } mKeyListView->flushKeys(); mKeyListView->setEnabled(true); mListJobCount = mTruncated = 0; mKeysToCheck.clear(); selectKeys(mKeyListView, mSelectedKeys); slotFilter(); connectSignals(); slotSelectionChanged(); // restore the saved position of the contents mKeyListView->verticalScrollBar()->setValue(mSavedOffsetY); mSavedOffsetY = 0; } void KeySelectionDialog::slotSelectionChanged() { qCDebug(KLEO_UI_LOG) << "KeySelectionDialog::slotSelectionChanged()"; // (re)start the check selection timer. Checking the selection is delayed // because else drag-selection doesn't work very good (checking key trust // is slow). mCheckSelectionTimer->start(sCheckSelectionDelay); } namespace { struct AlreadyChecked { bool operator()(const GpgME::Key &key) const { return key.keyListMode() & GpgME::Validate; } }; } void KeySelectionDialog::slotCheckSelection(KeyListViewItem *item) { qCDebug(KLEO_UI_LOG) << "KeySelectionDialog::slotCheckSelection()"; mCheckSelectionTimer->stop(); mSelectedKeys.clear(); if (!mKeyListView->isMultiSelection()) { if (item) { mSelectedKeys.push_back(item->key()); } } for (KeyListViewItem *it = mKeyListView->firstChild(); it; it = it->nextSibling()) { if (it->isSelected()) { mSelectedKeys.push_back(it->key()); } } mKeysToCheck.clear(); std::remove_copy_if(mSelectedKeys.begin(), mSelectedKeys.end(), std::back_inserter(mKeysToCheck), AlreadyChecked()); if (mKeysToCheck.empty()) { mOkButton->setEnabled(!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)); return; } // performed all fast checks - now for validating key listing: startValidatingKeyListing(); } void KeySelectionDialog::startValidatingKeyListing() { if (mKeysToCheck.empty()) { return; } mListJobCount = 0; mTruncated = 0; mSavedOffsetY = mKeyListView->verticalScrollBar()->value(); disconnectSignals(); mKeyListView->setEnabled(false); std::vector smime; std::vector openpgp; for (std::vector::const_iterator it = mKeysToCheck.begin(); it != mKeysToCheck.end(); ++it) { if (it->protocol() == GpgME::OpenPGP) { openpgp.push_back(*it); } else { smime.push_back(*it); } } if (!openpgp.empty()) { Q_ASSERT(mOpenPGPBackend); startKeyListJobForBackend(mOpenPGPBackend, openpgp, true /*validate*/); } if (!smime.empty()) { Q_ASSERT(mSMIMEBackend); startKeyListJobForBackend(mSMIMEBackend, smime, true /*validate*/); } Q_ASSERT(mListJobCount > 0); } bool KeySelectionDialog::rememberSelection() const { return mRememberCB && mRememberCB->isChecked(); } void KeySelectionDialog::slotRMB(KeyListViewItem *item, const QPoint &p) { if (!item) { return; } mCurrentContextMenuItem = item; QMenu menu; menu.addAction(i18n("Recheck Key"), this, &KeySelectionDialog::slotRecheckKey); menu.exec(p); } void KeySelectionDialog::slotRecheckKey() { if (!mCurrentContextMenuItem || mCurrentContextMenuItem->key().isNull()) { return; } mKeysToCheck.clear(); mKeysToCheck.push_back(mCurrentContextMenuItem->key()); } void KeySelectionDialog::slotTryOk() { if (!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)) { slotOk(); } } void KeySelectionDialog::slotOk() { if (mCheckSelectionTimer->isActive()) { slotCheckSelection(); } #if 0 // Laurent I don't understand why we returns here. // button could be disabled again after checking the selected key1 if (!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)) { return; } #endif mStartSearchTimer->stop(); accept(); } void KeySelectionDialog::slotCancel() { mCheckSelectionTimer->stop(); mStartSearchTimer->stop(); reject(); } void KeySelectionDialog::slotSearch(const QString &text) { mSearchText = text.trimmed().toUpper(); slotSearch(); } void KeySelectionDialog::slotSearch() { mStartSearchTimer->setSingleShot(true); mStartSearchTimer->start(sCheckSelectionDelay); } void KeySelectionDialog::slotFilter() { if (mSearchText.isEmpty()) { showAllItems(); return; } // OK, so we need to filter: QRegExp keyIdRegExp(QLatin1String("(?:0x)?[A-F0-9]{1,8}"), Qt::CaseInsensitive); if (keyIdRegExp.exactMatch(mSearchText)) { if (mSearchText.startsWith(QLatin1String("0X"))) // search for keyID only: { filterByKeyID(mSearchText.mid(2)); } else // search for UID and keyID: { filterByKeyIDOrUID(mSearchText); } } else { // search in UID: filterByUID(mSearchText); } } void KeySelectionDialog::filterByKeyID(const QString &keyID) { Q_ASSERT(keyID.length() <= 8); Q_ASSERT(!keyID.isEmpty()); // regexp in slotFilter should prevent these if (keyID.isEmpty()) { showAllItems(); } else { for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) { item->setHidden(!item->text(0).toUpper().startsWith(keyID)); } } } static bool anyUIDMatches(const KeyListViewItem *item, QRegExp &rx) { if (!item) { return false; } const std::vector uids = item->key().userIDs(); for (auto it = uids.begin(); it != uids.end(); ++it) { if (it->id() && rx.indexIn(QString::fromUtf8(it->id())) >= 0) { return true; } } return false; } void KeySelectionDialog::filterByKeyIDOrUID(const QString &str) { Q_ASSERT(!str.isEmpty()); // match beginnings of words: QRegExp rx(QLatin1String("\\b") + QRegExp::escape(str), Qt::CaseInsensitive); for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) { item->setHidden(!item->text(0).toUpper().startsWith(str) && !anyUIDMatches(item, rx)); } } void KeySelectionDialog::filterByUID(const QString &str) { Q_ASSERT(!str.isEmpty()); // match beginnings of words: QRegExp rx(QLatin1String("\\b") + QRegExp::escape(str), Qt::CaseInsensitive); for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) { item->setHidden(!anyUIDMatches(item, rx)); } } void KeySelectionDialog::showAllItems() { for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) { item->setHidden(false); } } diff --git a/src/ui/keyselectiondialog.h b/src/ui/keyselectiondialog.h index 2e3df1599..9b82957ca 100644 --- a/src/ui/keyselectiondialog.h +++ b/src/ui/keyselectiondialog.h @@ -1,202 +1,201 @@ /* -*- c++ -*- keyselectiondialog.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB Based on kpgpui.h SPDX-FileCopyrightText: 2001, 2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" #include #include -#include #include #include class QCheckBox; class QLabel; class QPoint; class QTimer; class QVBoxLayout; namespace Kleo { class KeyListView; class KeyListViewItem; } namespace GpgME { class KeyListResult; } namespace Kleo { class KLEO_EXPORT KeySelectionDialog : public QDialog { Q_OBJECT public: enum Option { // clang-format off RereadKeys = 0x01, ExternalCertificateManager = 0x02, ExtendedSelection = 0x04, RememberChoice = 0x08, // clang-format on }; Q_DECLARE_FLAGS(Options, Option) enum KeyUsage { // clang-format off PublicKeys = 1, SecretKeys = 2, EncryptionKeys = 4, SigningKeys = 8, ValidKeys = 16, TrustedKeys = 32, CertificationKeys = 64, AuthenticationKeys = 128, OpenPGPKeys = 256, SMIMEKeys = 512, AllKeys = PublicKeys | SecretKeys | OpenPGPKeys | SMIMEKeys, ValidEncryptionKeys = AllKeys | EncryptionKeys | ValidKeys, ValidTrustedEncryptionKeys = AllKeys | EncryptionKeys | ValidKeys | TrustedKeys // clang-format on }; explicit KeySelectionDialog(QWidget *parent = nullptr, Options options = Options()); KeySelectionDialog(const QString &title, const QString &text, const std::vector &selectedKeys = std::vector(), unsigned int keyUsage = AllKeys, bool extendedSelection = false, bool rememberChoice = false, QWidget *parent = nullptr, bool modal = true); KeySelectionDialog(const QString &title, const QString &text, const QString &initialPattern, const std::vector &selectedKeys, unsigned int keyUsage = AllKeys, bool extendedSelection = false, bool rememberChoice = false, QWidget *parent = nullptr, bool modal = true); KeySelectionDialog(const QString &title, const QString &text, const QString &initialPattern, unsigned int keyUsage = AllKeys, bool extendedSelection = false, bool rememberChoice = false, QWidget *parent = nullptr, bool modal = true); ~KeySelectionDialog() override; void setText(const QString &text); void setKeys(const std::vector &keys); /** Returns the key ID of the selected key in single selection mode. Otherwise it returns a null key. */ const GpgME::Key &selectedKey() const; QString fingerprint() const; /** Returns a list of selected key IDs. */ const std::vector &selectedKeys() const { return mSelectedKeys; } /// Return all the selected fingerprints QStringList fingerprints() const; /// Return the selected openpgp fingerprints QStringList pgpKeyFingerprints() const; /// Return the selected smime fingerprints QStringList smimeFingerprints() const; bool rememberSelection() const; // Could be used by derived classes to insert their own widget QVBoxLayout *topLayout() const { return mTopLayout; } private Q_SLOTS: void slotRereadKeys(); void slotStartCertificateManager(const QString &query = QString()); void slotStartSearchForExternalCertificates() { slotStartCertificateManager(mInitialQuery); } void slotKeyListResult(const GpgME::KeyListResult &); void slotSelectionChanged(); void slotCheckSelection() { slotCheckSelection(nullptr); } void slotCheckSelection(Kleo::KeyListViewItem *); void slotRMB(Kleo::KeyListViewItem *, const QPoint &); void slotRecheckKey(); void slotTryOk(); void slotOk(); void slotCancel(); void slotSearch(const QString &text); void slotSearch(); void slotFilter(); private: void filterByKeyID(const QString &keyID); void filterByKeyIDOrUID(const QString &keyID); void filterByUID(const QString &uid); void showAllItems(); void connectSignals(); void disconnectSignals(); void startKeyListJobForBackend(const QGpgME::Protocol *, const std::vector &, bool); void startValidatingKeyListing(); void setUpUI(Options options, const QString &); void init(bool, bool, const QString &, const QString &); private: QVBoxLayout *mTopLayout = nullptr; QLabel *mTextLabel = nullptr; Kleo::KeyListView *mKeyListView = nullptr; Kleo::KeyListViewItem *mCurrentContextMenuItem = nullptr; QCheckBox *mRememberCB = nullptr; QPushButton *mOkButton = nullptr; const QGpgME::Protocol *mOpenPGPBackend = nullptr; const QGpgME::Protocol *mSMIMEBackend = nullptr; std::vector mSelectedKeys, mKeysToCheck; unsigned int mKeyUsage; QTimer *mCheckSelectionTimer = nullptr; QTimer *mStartSearchTimer = nullptr; // cross-eventloop temporaries: QString mSearchText; const QString mInitialQuery; int mTruncated = 0; int mListJobCount = 0; int mSavedOffsetY = 0; }; } Q_DECLARE_OPERATORS_FOR_FLAGS(Kleo::KeySelectionDialog::Options) diff --git a/src/ui/newkeyapprovaldialog.cpp b/src/ui/newkeyapprovaldialog.cpp index 960d98037..140f581ab 100644 --- a/src/ui/newkeyapprovaldialog.cpp +++ b/src/ui/newkeyapprovaldialog.cpp @@ -1,920 +1,919 @@ /* -*- c++ -*- newkeyapprovaldialog.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "newkeyapprovaldialog.h" #include "keyselectioncombo.h" #include "progressdialog.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 using namespace Kleo; using namespace GpgME; namespace { class EncryptFilter : public DefaultKeyFilter { public: EncryptFilter() : DefaultKeyFilter() { setCanEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr s_encryptFilter = std::shared_ptr(new EncryptFilter); class OpenPGPFilter : public DefaultKeyFilter { public: OpenPGPFilter() : DefaultKeyFilter() { setIsOpenPGP(DefaultKeyFilter::Set); setCanEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr s_pgpEncryptFilter = std::shared_ptr(new OpenPGPFilter); class OpenPGPSignFilter : public DefaultKeyFilter { public: OpenPGPSignFilter() : DefaultKeyFilter() { /* Also list unusable keys to make it transparent why they are unusable */ setDisabled(DefaultKeyFilter::NotSet); setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setCanSign(DefaultKeyFilter::Set); setHasSecret(DefaultKeyFilter::Set); setIsOpenPGP(DefaultKeyFilter::Set); } }; static std::shared_ptr s_pgpSignFilter = std::shared_ptr(new OpenPGPSignFilter); class SMIMEFilter : public DefaultKeyFilter { public: SMIMEFilter() : DefaultKeyFilter() { setIsOpenPGP(DefaultKeyFilter::NotSet); setCanEncrypt(DefaultKeyFilter::Set); } }; static std::shared_ptr s_smimeEncryptFilter = std::shared_ptr(new SMIMEFilter); class SMIMESignFilter : public DefaultKeyFilter { public: SMIMESignFilter() : DefaultKeyFilter() { setDisabled(DefaultKeyFilter::NotSet); setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setCanSign(DefaultKeyFilter::Set); setIsOpenPGP(DefaultKeyFilter::NotSet); setHasSecret(DefaultKeyFilter::Set); } }; static std::shared_ptr s_smimeSignFilter = std::shared_ptr(new SMIMESignFilter); /* Some decoration and a button to remove the filter for a keyselectioncombo */ class ComboWidget : public QWidget { Q_OBJECT public: explicit ComboWidget(KeySelectionCombo *combo) : mCombo(combo) , mFilterBtn(new QPushButton) { auto hLay = new QHBoxLayout(this); auto infoBtn = new QPushButton; infoBtn->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual"))); infoBtn->setIconSize(QSize(22, 22)); infoBtn->setFlat(true); infoBtn->setAccessibleName(i18nc("@action:button", "Show Details")); hLay->addWidget(infoBtn); hLay->addWidget(combo, 1); hLay->addWidget(mFilterBtn, 0); connect(infoBtn, &QPushButton::clicked, this, [this, infoBtn]() { QToolTip::showText(infoBtn->mapToGlobal(QPoint()) + QPoint(infoBtn->width(), 0), mCombo->currentData(Qt::ToolTipRole).toString(), infoBtn, QRect(), 30000); }); // FIXME: This is ugly to enforce but otherwise the // icon is broken. combo->setMinimumHeight(22); mFilterBtn->setMinimumHeight(23); updateFilterButton(); connect(mFilterBtn, &QPushButton::clicked, this, [this]() { const QString curFilter = mCombo->idFilter(); if (curFilter.isEmpty()) { setIdFilter(mLastIdFilter); mLastIdFilter = QString(); } else { setIdFilter(QString()); mLastIdFilter = curFilter; } }); } void setIdFilter(const QString &id) { mCombo->setIdFilter(id); updateFilterButton(); } void updateFilterButton() { if (mCombo->idFilter().isEmpty()) { mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-add-filters"))); mFilterBtn->setAccessibleName(i18nc("@action:button", "Show Matching Keys")); mFilterBtn->setToolTip(i18n("Show keys matching the email address")); } else { mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters"))); mFilterBtn->setAccessibleName(i18nc("@action:button short for 'Show all keys'", "Show All")); mFilterBtn->setToolTip(i18n("Show all keys")); } } KeySelectionCombo *combo() { return mCombo; } GpgME::Protocol fixedProtocol() const { return mFixedProtocol; } void setFixedProtocol(GpgME::Protocol proto) { mFixedProtocol = proto; } private: KeySelectionCombo *mCombo; QPushButton *mFilterBtn; QString mLastIdFilter; GpgME::Protocol mFixedProtocol = GpgME::UnknownProtocol; }; static enum GpgME::UserID::Validity keyValidity(const GpgME::Key &key) { enum GpgME::UserID::Validity validity = GpgME::UserID::Validity::Unknown; for (const auto &uid : key.userIDs()) { if (validity == GpgME::UserID::Validity::Unknown || validity > uid.validity()) { validity = uid.validity(); } } return validity; } static bool key_has_addr(const GpgME::Key &key, const QString &addr) { for (const auto &uid : key.userIDs()) { if (QString::fromStdString(uid.addrSpec()).toLower() == addr.toLower()) { return true; } } return false; } bool anyKeyHasProtocol(const std::vector &keys, GpgME::Protocol protocol) { return std::any_of(std::begin(keys), std::end(keys), [protocol](const auto &key) { return key.protocol() == protocol; }); } Key findfirstKeyOfType(const std::vector &keys, GpgME::Protocol protocol) { const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol](const auto &key) { return key.protocol() == protocol; }); return it != std::end(keys) ? *it : Key(); } } // namespace class NewKeyApprovalDialog::Private { private: enum Action { Unset, GenerateKey, IgnoreKey, }; public: enum { OpenPGPButtonId = 1, SMIMEButtonId = 2, }; Private(NewKeyApprovalDialog *qq, bool encrypt, bool sign, GpgME::Protocol forcedProtocol, GpgME::Protocol presetProtocol, const QString &sender, bool allowMixed) : mForcedProtocol{forcedProtocol} , mSender{sender} , mSign{sign} , mEncrypt{encrypt} , mAllowMixed{allowMixed} , q{qq} { Q_ASSERT(forcedProtocol == GpgME::UnknownProtocol || presetProtocol == GpgME::UnknownProtocol || presetProtocol == forcedProtocol); Q_ASSERT(!allowMixed || (allowMixed && forcedProtocol == GpgME::UnknownProtocol)); Q_ASSERT(!(!allowMixed && presetProtocol == GpgME::UnknownProtocol)); // We do the translation here to avoid having the same string multiple times. mGenerateTooltip = i18nc( "@info:tooltip for a 'Generate new key pair' action " "in a combobox when a user does not yet have an OpenPGP or S/MIME key.", "Generate a new key using your E-Mail address.

" "The key is necessary to decrypt and sign E-Mails. " "You will be asked for a passphrase to protect this key and the protected key " "will be stored in your home directory."); mMainLay = new QVBoxLayout; QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); mOkButton = btnBox->button(QDialogButtonBox::Ok); #ifndef NDEBUG mOkButton->setObjectName(QStringLiteral("ok button")); #endif QObject::connect(btnBox, &QDialogButtonBox::accepted, q, [this]() { accepted(); }); QObject::connect(btnBox, &QDialogButtonBox::rejected, q, &QDialog::reject); mScrollArea = new QScrollArea; mScrollArea->setWidget(new QWidget); mScrollLayout = new QVBoxLayout; mScrollArea->widget()->setLayout(mScrollLayout); mScrollArea->setWidgetResizable(true); mScrollArea->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow); mScrollArea->setFrameStyle(QFrame::NoFrame); mScrollLayout->setContentsMargins(0, 0, 0, 0); q->setWindowTitle(i18nc("@title:window", "Security approval")); auto fmtLayout = new QHBoxLayout; mFormatBtns = new QButtonGroup(qq); QAbstractButton *pgpBtn; QAbstractButton *smimeBtn; if (mAllowMixed) { pgpBtn = new QCheckBox(i18n("OpenPGP")); smimeBtn = new QCheckBox(i18n("S/MIME")); } else { pgpBtn = new QRadioButton(i18n("OpenPGP")); smimeBtn = new QRadioButton(i18n("S/MIME")); } #ifndef NDEBUG pgpBtn->setObjectName(QStringLiteral("openpgp button")); smimeBtn->setObjectName(QStringLiteral("smime button")); #endif mFormatBtns->addButton(pgpBtn, OpenPGPButtonId); mFormatBtns->addButton(smimeBtn, SMIMEButtonId); mFormatBtns->setExclusive(!mAllowMixed); fmtLayout->addStretch(-1); fmtLayout->addWidget(pgpBtn); fmtLayout->addWidget(smimeBtn); mMainLay->addLayout(fmtLayout); if (mForcedProtocol != GpgME::UnknownProtocol) { pgpBtn->setChecked(mForcedProtocol == GpgME::OpenPGP); smimeBtn->setChecked(mForcedProtocol == GpgME::CMS); pgpBtn->setVisible(false); smimeBtn->setVisible(false); } else { pgpBtn->setChecked(presetProtocol == GpgME::OpenPGP || presetProtocol == GpgME::UnknownProtocol); smimeBtn->setChecked(presetProtocol == GpgME::CMS || presetProtocol == GpgME::UnknownProtocol); } QObject::connect(mFormatBtns, &QButtonGroup::idClicked, q, [this](int buttonId) { // ensure that at least one protocol button is checked if (mAllowMixed && !mFormatBtns->button(OpenPGPButtonId)->isChecked() && !mFormatBtns->button(SMIMEButtonId)->isChecked()) { mFormatBtns->button(buttonId == OpenPGPButtonId ? SMIMEButtonId : OpenPGPButtonId)->setChecked(true); } updateWidgets(); }); mMainLay->addWidget(mScrollArea); mComplianceLbl = new QLabel; mComplianceLbl->setVisible(false); #ifndef NDEBUG mComplianceLbl->setObjectName(QStringLiteral("compliance label")); #endif auto btnLayout = new QHBoxLayout; btnLayout->addWidget(mComplianceLbl); btnLayout->addWidget(btnBox); mMainLay->addLayout(btnLayout); q->setLayout(mMainLay); } ~Private() = default; GpgME::Protocol currentProtocol() { const bool openPGPButtonChecked = mFormatBtns->button(OpenPGPButtonId)->isChecked(); const bool smimeButtonChecked = mFormatBtns->button(SMIMEButtonId)->isChecked(); if (mAllowMixed) { if (openPGPButtonChecked && !smimeButtonChecked) { return GpgME::OpenPGP; } if (!openPGPButtonChecked && smimeButtonChecked) { return GpgME::CMS; } } else if (openPGPButtonChecked) { return GpgME::OpenPGP; } else if (smimeButtonChecked) { return GpgME::CMS; } return GpgME::UnknownProtocol; } auto findVisibleKeySelectionComboWithGenerateKey() { const auto it = std::find_if(std::begin(mAllCombos), std::end(mAllCombos), [](auto combo) { return combo->isVisible() && combo->currentData(Qt::UserRole).toInt() == GenerateKey; }); return it != std::end(mAllCombos) ? *it : nullptr; } void generateKey(KeySelectionCombo *combo) { const auto &addr = combo->property("address").toString(); auto job = new QGpgME::DefaultKeyGenerationJob(q); auto progress = new Kleo::ProgressDialog(job, i18n("Generating key for '%1'...", addr) + QStringLiteral("\n\n") + i18n("This can take several minutes."), q); progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowContextHelpButtonHint); progress->setWindowTitle(i18nc("@title:window", "Key generation")); progress->setModal(true); progress->setAutoClose(true); progress->setMinimumDuration(0); progress->setValue(0); mRunningJobs << job; connect(job, &QGpgME::DefaultKeyGenerationJob::result, q, [this, job, combo](const GpgME::KeyGenerationResult &result) { handleKeyGenResult(result, job, combo); }); job->start(addr, QString()); return; } void handleKeyGenResult(const GpgME::KeyGenerationResult &result, QGpgME::Job *job, KeySelectionCombo *combo) { mLastError = result.error(); if (!mLastError || mLastError.isCanceled()) { combo->setDefaultKey(QString::fromLatin1(result.fingerprint()), GpgME::OpenPGP); connect(combo, &KeySelectionCombo::keyListingFinished, q, [this, job]() { mRunningJobs.removeAll(job); }); combo->refreshKeys(); } else { mRunningJobs.removeAll(job); } } void checkAccepted() { if (mLastError || mLastError.isCanceled()) { KMessageBox::error(q, QString::fromLocal8Bit(mLastError.asString()), i18n("Operation Failed")); mRunningJobs.clear(); return; } if (!mRunningJobs.empty()) { return; } /* Save the keys */ mAcceptedResult.protocol = currentProtocol(); for (const auto combo : std::as_const(mEncCombos)) { const auto addr = combo->property("address").toString(); const auto key = combo->currentKey(); if (!combo->isVisible() || key.isNull()) { continue; } mAcceptedResult.encryptionKeys[addr].push_back(key); } for (const auto combo : std::as_const(mSigningCombos)) { const auto key = combo->currentKey(); if (!combo->isVisible() || key.isNull()) { continue; } mAcceptedResult.signingKeys.push_back(key); } q->accept(); } void accepted() { // We can assume everything was validly resolved, otherwise // the OK button would have been disabled. // Handle custom items now. if (auto combo = findVisibleKeySelectionComboWithGenerateKey()) { generateKey(combo); return; } checkAccepted(); } auto encryptionKeyFilter(GpgME::Protocol protocol) { switch (protocol) { case OpenPGP: return s_pgpEncryptFilter; case CMS: return s_smimeEncryptFilter; default: return s_encryptFilter; } } void updateWidgets() { const GpgME::Protocol protocol = currentProtocol(); const auto encryptionFilter = encryptionKeyFilter(protocol); for (auto combo : std::as_const(mSigningCombos)) { auto widget = qobject_cast(combo->parentWidget()); if (!widget) { qCDebug(LIBKLEO_LOG) << "Failed to find signature combo widget"; continue; } widget->setVisible(protocol == GpgME::UnknownProtocol || widget->fixedProtocol() == GpgME::UnknownProtocol || widget->fixedProtocol() == protocol); } for (auto combo : std::as_const(mEncCombos)) { auto widget = qobject_cast(combo->parentWidget()); if (!widget) { qCDebug(LIBKLEO_LOG) << "Failed to find combo widget"; continue; } widget->setVisible(protocol == GpgME::UnknownProtocol || widget->fixedProtocol() == GpgME::UnknownProtocol || widget->fixedProtocol() == protocol); if (widget->isVisible() && combo->property("address") != mSender) { combo->setKeyFilter(encryptionFilter); } } // hide the labels indicating the protocol of the sender's keys if only a single protocol is active const auto protocolLabels = q->findChildren(QStringLiteral("protocol label")); for (auto label : protocolLabels) { label->setVisible(protocol == GpgME::UnknownProtocol); } } auto createProtocolLabel(GpgME::Protocol protocol) { auto label = new QLabel(Formatting::displayName(protocol)); label->setObjectName(QStringLiteral("protocol label")); return label; } ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key, GpgME::Protocol protocol = GpgME::UnknownProtocol) { Q_ASSERT(!key.isNull() || protocol != UnknownProtocol); protocol = !key.isNull() ? key.protocol() : protocol; auto combo = new KeySelectionCombo(); auto comboWidget = new ComboWidget(combo); #ifndef NDEBUG combo->setObjectName(QStringLiteral("signing key")); #endif if (protocol == GpgME::OpenPGP) { combo->setKeyFilter(s_pgpSignFilter); } else if (protocol == GpgME::CMS) { combo->setKeyFilter(s_smimeSignFilter); } if (key.isNull() || key_has_addr(key, mSender)) { comboWidget->setIdFilter(mSender); } comboWidget->setFixedProtocol(protocol); if (!key.isNull()) { combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), protocol); } if (key.isNull() && protocol == OpenPGP) { combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey, mGenerateTooltip); } combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("emblem-unavailable")), i18n("Don't confirm identity and integrity"), IgnoreKey, i18nc("@info:tooltip for not selecting a key for signing.", "The E-Mail will not be cryptographically signed.")); mSigningCombos << combo; mAllCombos << combo; combo->setProperty("address", addr); connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this]() { updateOkButton(); }); connect(combo, qOverload(&QComboBox::currentIndexChanged), q, [this]() { updateOkButton(); }); return comboWidget; } void setSigningKeys(const std::vector &preferredKeys, const std::vector &alternativeKeys) { auto group = new QGroupBox(i18nc("Caption for signing key selection", "Confirm identity '%1' as:", mSender)); group->setAlignment(Qt::AlignLeft); auto sigLayout = new QVBoxLayout(group); const bool mayNeedOpenPGP = mForcedProtocol != CMS; const bool mayNeedCMS = mForcedProtocol != OpenPGP; if (mayNeedOpenPGP) { if (mAllowMixed) { sigLayout->addWidget(createProtocolLabel(OpenPGP)); } const Key preferredKey = findfirstKeyOfType(preferredKeys, OpenPGP); const Key alternativeKey = findfirstKeyOfType(alternativeKeys, OpenPGP); if (!preferredKey.isNull()) { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey; auto comboWidget = createSigningCombo(mSender, preferredKey); sigLayout->addWidget(comboWidget); } else if (!alternativeKey.isNull()) { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey; auto comboWidget = createSigningCombo(mSender, alternativeKey); sigLayout->addWidget(comboWidget); } else { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for OpenPGP key"; auto comboWidget = createSigningCombo(mSender, Key(), OpenPGP); sigLayout->addWidget(comboWidget); } } if (mayNeedCMS) { if (mAllowMixed) { sigLayout->addWidget(createProtocolLabel(CMS)); } const Key preferredKey = findfirstKeyOfType(preferredKeys, CMS); const Key alternativeKey = findfirstKeyOfType(alternativeKeys, CMS); if (!preferredKey.isNull()) { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey; auto comboWidget = createSigningCombo(mSender, preferredKey); sigLayout->addWidget(comboWidget); } else if (!alternativeKey.isNull()) { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey; auto comboWidget = createSigningCombo(mSender, alternativeKey); sigLayout->addWidget(comboWidget); } else { qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for S/MIME key"; auto comboWidget = createSigningCombo(mSender, Key(), CMS); sigLayout->addWidget(comboWidget); } } mScrollLayout->addWidget(group); } ComboWidget *createEncryptionCombo(const QString &addr, const GpgME::Key &key, GpgME::Protocol fixedProtocol) { auto combo = new KeySelectionCombo(false); auto comboWidget = new ComboWidget(combo); #ifndef NDEBUG combo->setObjectName(QStringLiteral("encryption key")); #endif if (fixedProtocol == GpgME::OpenPGP) { combo->setKeyFilter(s_pgpEncryptFilter); } else if (fixedProtocol == GpgME::CMS) { combo->setKeyFilter(s_smimeEncryptFilter); } else { combo->setKeyFilter(s_encryptFilter); } if (key.isNull() || key_has_addr(key, addr)) { comboWidget->setIdFilter(addr); } comboWidget->setFixedProtocol(fixedProtocol); if (!key.isNull()) { combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), fixedProtocol); } if (addr == mSender && key.isNull() && fixedProtocol == OpenPGP) { combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey, mGenerateTooltip); } combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("emblem-unavailable")), i18n("No key. Recipient will be unable to decrypt."), IgnoreKey, i18nc("@info:tooltip for No Key selected for a specific recipient.", "Do not select a key for this recipient.

" "The recipient will receive the encrypted E-Mail, but it can only " "be decrypted with the other keys selected in this dialog.")); connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this]() { updateOkButton(); }); connect(combo, qOverload(&QComboBox::currentIndexChanged), q, [this]() { updateOkButton(); }); mEncCombos << combo; mAllCombos << combo; combo->setProperty("address", addr); return comboWidget; } void addEncryptionAddr(const QString &addr, GpgME::Protocol preferredKeysProtocol, const std::vector &preferredKeys, GpgME::Protocol alternativeKeysProtocol, const std::vector &alternativeKeys, QGridLayout *encGrid) { if (addr == mSender) { const bool mayNeedOpenPGP = mForcedProtocol != CMS; const bool mayNeedCMS = mForcedProtocol != OpenPGP; if (mayNeedOpenPGP) { if (mAllowMixed) { encGrid->addWidget(createProtocolLabel(OpenPGP), encGrid->rowCount(), 0); } for (const auto &key : preferredKeys) { if (key.protocol() == OpenPGP) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, OpenPGP); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } for (const auto &key : alternativeKeys) { if (key.protocol() == OpenPGP) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, OpenPGP); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } if (!anyKeyHasProtocol(preferredKeys, OpenPGP) && !anyKeyHasProtocol(alternativeKeys, OpenPGP)) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for OpenPGP key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), OpenPGP); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } if (mayNeedCMS) { if (mAllowMixed) { encGrid->addWidget(createProtocolLabel(CMS), encGrid->rowCount(), 0); } for (const auto &key : preferredKeys) { if (key.protocol() == CMS) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, CMS); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } for (const auto &key : alternativeKeys) { if (key.protocol() == CMS) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, CMS); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } if (!anyKeyHasProtocol(preferredKeys, CMS) && !anyKeyHasProtocol(alternativeKeys, CMS)) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for S/MIME key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), CMS); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } } else { encGrid->addWidget(new QLabel(addr), encGrid->rowCount(), 0); for (const auto &key : preferredKeys) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, preferredKeysProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } for (const auto &key : alternativeKeys) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key; auto comboWidget = createEncryptionCombo(addr, key, alternativeKeysProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } if (!mAllowMixed) { if (preferredKeys.empty()) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(preferredKeysProtocol) << "key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), preferredKeysProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } if (alternativeKeys.empty() && alternativeKeysProtocol != UnknownProtocol) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(alternativeKeysProtocol) << "key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), alternativeKeysProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } else { if (preferredKeys.empty() && alternativeKeys.empty()) { qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for any key"; auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), UnknownProtocol); encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2); } } } } void setEncryptionKeys(GpgME::Protocol preferredKeysProtocol, const QMap> &preferredKeys, GpgME::Protocol alternativeKeysProtocol, const QMap> &alternativeKeys) { { auto group = new QGroupBox(i18nc("Encrypt to self (email address):", "Encrypt to self (%1):", mSender)); #ifndef NDEBUG group->setObjectName(QStringLiteral("encrypt-to-self box")); #endif group->setAlignment(Qt::AlignLeft); auto encGrid = new QGridLayout(group); addEncryptionAddr(mSender, preferredKeysProtocol, preferredKeys.value(mSender), alternativeKeysProtocol, alternativeKeys.value(mSender), encGrid); encGrid->setColumnStretch(1, -1); mScrollLayout->addWidget(group); } const bool hasOtherRecipients = std::any_of(preferredKeys.keyBegin(), preferredKeys.keyEnd(), [this](const auto &recipient) { return recipient != mSender; }); if (hasOtherRecipients) { auto group = new QGroupBox(i18n("Encrypt to others:")); #ifndef NDEBUG group->setObjectName(QStringLiteral("encrypt-to-others box")); #endif group->setAlignment(Qt::AlignLeft); auto encGrid = new QGridLayout{group}; for (auto it = std::begin(preferredKeys); it != std::end(preferredKeys); ++it) { const auto &address = it.key(); const auto &keys = it.value(); if (address != mSender) { addEncryptionAddr(address, preferredKeysProtocol, keys, alternativeKeysProtocol, alternativeKeys.value(address), encGrid); } } encGrid->setColumnStretch(1, -1); mScrollLayout->addWidget(group); } mScrollLayout->addStretch(-1); } void updateOkButton() { static QString origOkText = mOkButton->text(); const bool isGenerate = bool(findVisibleKeySelectionComboWithGenerateKey()); const bool allVisibleEncryptionKeysAreIgnored = std::all_of(std::begin(mEncCombos), std::end(mEncCombos), [](auto combo) { return !combo->isVisible() || combo->currentData(Qt::UserRole).toInt() == IgnoreKey; }); // If we don't encrypt the ok button is always enabled. But otherwise // we only enable it if we encrypt to at least one recipient. mOkButton->setEnabled(!mEncrypt || !allVisibleEncryptionKeysAreIgnored); mOkButton->setText(isGenerate ? i18n("Generate") : origOkText); if (!DeVSCompliance::isActive()) { return; } // Handle compliance bool de_vs = DeVSCompliance::isCompliant(); if (de_vs) { const GpgME::Protocol protocol = currentProtocol(); for (const auto combo : std::as_const(mAllCombos)) { if (!combo->isVisible()) { continue; } const auto key = combo->currentKey(); if (key.isNull()) { continue; } if (protocol != GpgME::UnknownProtocol && key.protocol() != protocol) { continue; } if (!Formatting::isKeyDeVs(key) || keyValidity(key) < GpgME::UserID::Validity::Full) { de_vs = false; break; } } } DeVSCompliance::decorate(mOkButton, de_vs); mComplianceLbl->setText(DeVSCompliance::name(de_vs)); mComplianceLbl->setVisible(true); } GpgME::Protocol mForcedProtocol; QList mSigningCombos; QList mEncCombos; QList mAllCombos; QScrollArea *mScrollArea; QVBoxLayout *mScrollLayout; QPushButton *mOkButton; QVBoxLayout *mMainLay; QButtonGroup *mFormatBtns; QString mSender; bool mSign; bool mEncrypt; bool mAllowMixed; NewKeyApprovalDialog *q; QList mRunningJobs; GpgME::Error mLastError; QLabel *mComplianceLbl; KeyResolver::Solution mAcceptedResult; QString mGenerateTooltip; }; NewKeyApprovalDialog::NewKeyApprovalDialog(bool encrypt, bool sign, const QString &sender, KeyResolver::Solution preferredSolution, KeyResolver::Solution alternativeSolution, bool allowMixed, GpgME::Protocol forcedProtocol, QWidget *parent, Qt::WindowFlags f) : QDialog(parent, f) , d{std::make_unique(this, encrypt, sign, forcedProtocol, preferredSolution.protocol, sender, allowMixed)} { if (sign) { d->setSigningKeys(std::move(preferredSolution.signingKeys), std::move(alternativeSolution.signingKeys)); } if (encrypt) { d->setEncryptionKeys(allowMixed ? UnknownProtocol : preferredSolution.protocol, std::move(preferredSolution.encryptionKeys), allowMixed ? UnknownProtocol : alternativeSolution.protocol, std::move(alternativeSolution.encryptionKeys)); } d->updateWidgets(); d->updateOkButton(); const auto size = sizeHint(); const auto desk = screen()->size(); resize(QSize(desk.width() / 3, qMin(size.height(), desk.height() / 2))); } Kleo::NewKeyApprovalDialog::~NewKeyApprovalDialog() = default; KeyResolver::Solution NewKeyApprovalDialog::result() { return d->mAcceptedResult; } #include "newkeyapprovaldialog.moc" diff --git a/src/utils/systeminfo.cpp b/src/utils/systeminfo.cpp index 6540ead75..4ebc733c6 100644 --- a/src/utils/systeminfo.cpp +++ b/src/utils/systeminfo.cpp @@ -1,96 +1,95 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/systeminfo.cpp This file is part of libkleopatra SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "systeminfo.h" #include #include -#include // #include "libkleo_debug.h" #ifdef Q_OS_WIN #include "windows.h" // #include "gnupg-registry.h" #endif #ifdef Q_OS_WIN namespace { bool win_isHighContrastModeActive() { HIGHCONTRAST result; result.cbSize = sizeof(HIGHCONTRAST); if (SystemParametersInfo(SPI_GETHIGHCONTRAST, result.cbSize, &result, 0)) { return (result.dwFlags & HCF_HIGHCONTRASTON); } return false; } bool win_isDarkModeActive() { /* This is a bit complicated. Qt does not detect this correctly * for high contrast mode with white contrast. */ // First check for white background. That is set in High contrast // white theme. DWORD color = GetSysColor(COLOR_WINDOW); if (color == 0xFFFFFF) { return false; } // Windows 10 has only one white High Contrast mode. The other // three are dark. if (win_isHighContrastModeActive()) { return true; } #if 0 // This is not enabled because although Qt does check for this, the theme // does not switch accordingly in tests. So we would have white icons on a // bright window. // // The user may have customized a dark theme. Then AppsUseLightTheme is 0 char *val = read_w32_registry_string ("HKEY_CURRENT_USER", "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", "AppsUseLightTheme"); bool ret = false; if (val) { ret = !((DWORD) *val); free (val); return ret; } #endif // Nothing set -> default to bright. return false; } } #endif bool Kleo::SystemInfo::isHighContrastModeActive() { static bool forceHighContrastMode = qgetenv("KLEO_HIGH_CONTRAST_MODE").toInt(); #ifdef Q_OS_WIN static bool highContrastModeActive = forceHighContrastMode || win_isHighContrastModeActive(); return highContrastModeActive; #else return forceHighContrastMode; #endif } bool Kleo::SystemInfo::isDarkModeActive() { #ifdef Q_OS_WIN return win_isDarkModeActive(); #else // Don't know return isHighContrastModeActive(); #endif } diff --git a/src/utils/uniquelock.cpp b/src/utils/uniquelock.cpp index 324e5d5d5..f42a8a0c2 100644 --- a/src/utils/uniquelock.cpp +++ b/src/utils/uniquelock.cpp @@ -1,155 +1,153 @@ /* utils/uniquelock.cpp QMutex-compatible replacement for std::unique_lock This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2008-2021 Free Software Foundation, Inc. SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-3.0-or-later WITH GCC-exception-3.1 */ #include #include "uniquelock.h" #include -#include - namespace Kleo { UniqueLock::UniqueLock() noexcept : mMutex{nullptr} , mOwnsMutex{false} { } UniqueLock::UniqueLock(QMutex &mutex) : mMutex{std::addressof(mutex)} , mOwnsMutex{false} { lock(); mOwnsMutex = true; } UniqueLock::UniqueLock(QMutex &mutex, DeferLockType) noexcept : mMutex{std::addressof(mutex)} , mOwnsMutex{false} { } UniqueLock::UniqueLock(QMutex &mutex, TryToLockType) : mMutex{std::addressof(mutex)} , mOwnsMutex{mMutex->try_lock()} { } UniqueLock::UniqueLock(QMutex &mutex, AdoptLockType) noexcept : mMutex{std::addressof(mutex)} , mOwnsMutex{true} { // XXX calling thread owns mutex } UniqueLock::~UniqueLock() { if (mOwnsMutex) { unlock(); } } UniqueLock::UniqueLock(UniqueLock &&u) noexcept : mMutex{u.mMutex} , mOwnsMutex{u.mOwnsMutex} { u.mMutex = nullptr; u.mOwnsMutex = false; } UniqueLock &UniqueLock::operator=(UniqueLock &&u) noexcept { if (mOwnsMutex) { unlock(); } UniqueLock(std::move(u)).swap(*this); u.mMutex = nullptr; u.mOwnsMutex = false; return *this; } void UniqueLock::lock() { Q_ASSERT(mMutex); Q_ASSERT(!mOwnsMutex); if (!mMutex) { qCWarning(LIBKLEO_LOG) << __func__ << "Error: operation not permitted"; } else if (mOwnsMutex) { qCWarning(LIBKLEO_LOG) << __func__ << "Error: resource deadlock would occur"; } else { mMutex->lock(); mOwnsMutex = true; } } bool UniqueLock::try_lock() { Q_ASSERT(mMutex); Q_ASSERT(!mOwnsMutex); if (!mMutex) { qCWarning(LIBKLEO_LOG) << __func__ << "Error: operation not permitted"; return false; } else if (mOwnsMutex) { qCWarning(LIBKLEO_LOG) << __func__ << "Error: resource deadlock would occur"; return false; } else { mOwnsMutex = mMutex->try_lock(); return mOwnsMutex; } } void UniqueLock::unlock() { if (!mOwnsMutex) { qCWarning(LIBKLEO_LOG) << __func__ << "Error: operation not permitted"; } else if (mMutex) { mMutex->unlock(); mOwnsMutex = false; } } void UniqueLock::swap(UniqueLock &u) noexcept { std::swap(mMutex, u.mMutex); std::swap(mOwnsMutex, u.mOwnsMutex); } QMutex *UniqueLock::release() noexcept { QMutex *ret = mMutex; mMutex = nullptr; mOwnsMutex = false; return ret; } bool UniqueLock::owns_lock() const noexcept { return mOwnsMutex; } UniqueLock::operator bool() const noexcept { return owns_lock(); } QMutex *UniqueLock::mutex() const noexcept { return mMutex; } } // namespace Kleo diff --git a/src/utils/uniquelock.h b/src/utils/uniquelock.h index abbb32697..65bb7bf00 100644 --- a/src/utils/uniquelock.h +++ b/src/utils/uniquelock.h @@ -1,145 +1,144 @@ /* utils/uniquelock.h QMutex-compatible replacement for std::unique_lock This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2008-2021 Free Software Foundation, Inc. SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-3.0-or-later WITH GCC-exception-3.1 */ #pragma once #include "kleo_export.h" #include -#include #include #include namespace Kleo { /// Do not acquire ownership of the mutex. struct DeferLockType { explicit DeferLockType() = default; }; /// Try to acquire ownership of the mutex without blocking. struct TryToLockType { explicit TryToLockType() = default; }; /// Assume the calling thread has already obtained mutex ownership /// and manage it. struct AdoptLockType { explicit AdoptLockType() = default; }; /// Tag used to prevent a scoped lock from acquiring ownership of a mutex. inline constexpr DeferLockType deferLock{}; /// Tag used to prevent a scoped lock from blocking if a mutex is locked. inline constexpr TryToLockType tryToLock{}; /// Tag used to make a scoped lock take ownership of a locked mutex. inline constexpr AdoptLockType adoptLock{}; /** @brief A movable scoped lock type for QMutex. * * A UniqueLock controls mutex ownership within a scope. Ownership of the * mutex can be delayed until after construction and can be transferred * to another UniqueLock by move construction or move assignment. If a * mutex lock is owned when the destructor runs ownership will be released. */ class KLEO_EXPORT UniqueLock { public: UniqueLock() noexcept; explicit UniqueLock(QMutex &mutex); UniqueLock(QMutex &mutex, DeferLockType) noexcept; UniqueLock(QMutex &mutex, TryToLockType); UniqueLock(QMutex &mutex, AdoptLockType) noexcept; template UniqueLock(QMutex &mutex, const std::chrono::time_point &timePoint) : mMutex{std::addressof(mutex)} , mOwnsMutex{mMutex->try_lock_until(timePoint)} { } template UniqueLock(QMutex &mutex, const std::chrono::duration &duration) : mMutex{std::addressof(mutex)} , mOwnsMutex{mMutex->try_lock_for(duration)} { } ~UniqueLock(); UniqueLock(const UniqueLock &) = delete; UniqueLock &operator=(const UniqueLock &) = delete; UniqueLock(UniqueLock &&u) noexcept; UniqueLock &operator=(UniqueLock &&u) noexcept; void lock(); bool try_lock(); template bool try_lock_until(const std::chrono::time_point &timePoint) { Q_ASSERT(mMutex); Q_ASSERT(!mOwnsMutex); if (mMutex && !mOwnsMutex) { mOwnsMutex = mMutex->try_lock_until(timePoint); return mOwnsMutex; } } template bool try_lock_for(const std::chrono::duration &duration) { Q_ASSERT(mMutex); Q_ASSERT(!mOwnsMutex); if (mMutex && !mOwnsMutex) { mOwnsMutex = mMutex->try_lock_for(duration); return mOwnsMutex; } } void unlock(); void swap(UniqueLock &u) noexcept; QMutex *release() noexcept; bool owns_lock() const noexcept; explicit operator bool() const noexcept; QMutex *mutex() const noexcept; private: QMutex *mMutex; bool mOwnsMutex; }; } // namespace Kleo namespace std { /// Swap overload for UniqueLock objects. /// @relates UniqueLock inline void swap(Kleo::UniqueLock &x, Kleo::UniqueLock &y) noexcept { x.swap(y); } }