diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index e0c8e78c1..df59971b2 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,37 +1,42 @@ remove_definitions(-DQT_NO_CAST_FROM_ASCII) include(ECMAddTests) find_package(Qt5Test ${REQUIRED_QT_VERSION} CONFIG QUIET) if(NOT Qt5Test_FOUND) message(STATUS "Qt5Test not found, autotests will not be built.") return() endif() ecm_add_test( flatkeylistmodeltest.cpp abstractkeylistmodeltest.cpp TEST_NAME flatkeylistmodeltest LINK_LIBRARIES KF5::Libkleo Qt::Test ) ecm_add_test( hierarchicalkeylistmodeltest.cpp abstractkeylistmodeltest.cpp TEST_NAME hierarchicalkeylistmodeltest LINK_LIBRARIES KF5::Libkleo Qt::Test ) ecm_add_test( keyresolvercoretest.cpp keyresolvercoretest.qrc TEST_NAME keyresolvercoretest LINK_LIBRARIES KF5::Libkleo Qt::Test ) +ecm_add_tests( + editdirectoryservicedialogtest.cpp + LINK_LIBRARIES KF5::Libkleo KF5::WidgetsAddons Qt::Widgets Qt::Test +) + ecm_add_tests( keyserverconfigtest.cpp newkeyapprovaldialogtest.cpp LINK_LIBRARIES KF5::Libkleo Qt::Widgets Qt::Test ) diff --git a/autotests/editdirectoryservicedialogtest.cpp b/autotests/editdirectoryservicedialogtest.cpp new file mode 100644 index 000000000..ce68d1b93 --- /dev/null +++ b/autotests/editdirectoryservicedialogtest.cpp @@ -0,0 +1,593 @@ +/* + 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 ) \ + WHEN_USER_SELECTS_BUTTON_WITH_ID_IN_BUTTON_GROUP("authenticationGroup", static_cast(authentication)); + +#define WHEN_USER_SELECTS_CONNECTION( connection ) \ + WHEN_USER_SELECTS_BUTTON_WITH_ID_IN_BUTTON_GROUP("connectionGroup", static_cast(connection)); + +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/CMakeLists.txt b/src/CMakeLists.txt index 70969fda8..b01e0b978 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,237 +1,239 @@ # target_include_directories does not handle empty include paths include_directories(${GPGME_INCLUDES}) add_definitions(-DTRANSLATION_DOMAIN=\"libkleopatra\") #add_definitions( -DQT_NO_CAST_FROM_ASCII ) #add_definitions( -DQT_NO_CAST_TO_ASCII ) kde_enable_exceptions() add_definitions( -DGPGMEPP_ERR_SOURCE_DEFAULT=13 ) # 13 is GPG_ERR_SOURCE_KLEO, even if gpg-error's too old to know about add_subdirectory( pics ) if (BUILD_TESTING) add_subdirectory( tests ) endif() add_library(KF5Libkleo) add_library(KF5::Libkleo ALIAS KF5Libkleo) ########### next target ############### target_sources(KF5Libkleo PRIVATE kleo/checksumdefinition.cpp kleo/debug.cpp kleo/defaultkeyfilter.cpp kleo/defaultkeygenerationjob.cpp kleo/dn.cpp kleo/enum.cpp kleo/kconfigbasedkeyfilter.cpp kleo/keyfiltermanager.cpp kleo/keygroup.cpp kleo/keyresolver.cpp kleo/keyresolvercore.cpp kleo/keyserverconfig.cpp kleo/kleoexception.cpp kleo/oidmap.cpp models/keycache.cpp models/keylistmodel.cpp models/keylistmodelinterface.cpp models/keylistsortfilterproxymodel.cpp models/keyrearrangecolumnsproxymodel.cpp models/subkeylistmodel.cpp models/useridlistmodel.cpp utils/filesystemwatcher.cpp utils/formatting.cpp utils/classify.cpp utils/gnupg.cpp utils/gnupg-registry.c utils/hex.cpp utils/compat.cpp utils/cryptoconfig.cpp utils/test.cpp ) ecm_qt_declare_logging_category(KF5Libkleo HEADER libkleo_debug.h IDENTIFIER LIBKLEO_LOG CATEGORY_NAME org.kde.pim.libkleo DESCRIPTION "libkleo (kleo_core)" EXPORT LIBKLEO ) target_sources(KF5Libkleo PRIVATE ui/dnattributeorderconfigwidget.cpp ui/kdhorizontalline.cpp ui/filenamerequester.cpp ui/messagebox.cpp ui/cryptoconfigmodule.cpp ui/directoryserviceswidget.cpp ui/progressbar.cpp ui/progressdialog.cpp ui/auditlogviewer.cpp + ui/editdirectoryservicedialog.cpp ) ecm_qt_declare_logging_category(KF5Libkleo HEADER kleo_ui_debug.h IDENTIFIER KLEO_UI_LOG CATEGORY_NAME org.kde.pim.kleo_ui DESCRIPTION "libkleo (kleo_ui)" OLD_CATEGORY_NAMES log_kleo_ui EXPORT LIBKLEO ) target_sources(KF5Libkleo PRIVATE # make this a separate lib. ui/keylistview.cpp ui/keyselectiondialog.cpp ui/keyrequester.cpp ui/keyapprovaldialog.cpp ui/newkeyapprovaldialog.cpp ui/keyselectioncombo.cpp ) ki18n_wrap_ui(KF5Libkleo ui/directoryserviceswidget.ui ) target_link_libraries(KF5Libkleo PUBLIC QGpgme Gpgmepp PRIVATE Qt::Widgets KF5::I18n KF5::Completion KF5::ConfigCore KF5::CoreAddons KF5::WidgetsAddons KF5::ItemModels KF5::Codecs) if (KF5PimTextEdit_FOUND) add_definitions(-DHAVE_PIMTEXTEDIT) target_link_libraries(KF5Libkleo PRIVATE KF5::PimTextEdit) endif() if (COMPILE_WITH_UNITY_CMAKE_SUPPORT) set_target_properties(KF5Libkleo PROPERTIES UNITY_BUILD ON) endif() generate_export_header(KF5Libkleo BASE_NAME kleo) if(WIN32) target_link_libraries(KF5Libkleo ${GPGME_VANILLA_LIBRARIES} ) endif() set_target_properties(KF5Libkleo PROPERTIES VERSION ${LIBKLEO_VERSION} SOVERSION ${LIBKLEO_SOVERSION} EXPORT_NAME Libkleo ) install(TARGETS KF5Libkleo EXPORT KF5LibkleoTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ) target_include_directories(KF5Libkleo PUBLIC "$") target_include_directories(KF5Libkleo INTERFACE "$") ecm_generate_headers(libkleo_CamelCase_HEADERS HEADER_NAMES ChecksumDefinition Debug DefaultKeyFilter DefaultKeyGenerationJob Dn Enum KConfigBasedKeyFilter KeyFilter KeyFilterManager KeyGroup KeyResolver KeyResolverCore KeyserverConfig KleoException OidMap Predicates Stl_Util REQUIRED_HEADERS libkleo_HEADERS PREFIX Libkleo RELATIVE kleo ) ecm_generate_headers(libkleo_CamelCase_models_HEADERS HEADER_NAMES KeyCache KeyList KeyListModel KeyListModelInterface KeyListSortFilterProxyModel KeyRearrangeColumnsProxyModel SubkeyListModel UserIDListModel REQUIRED_HEADERS libkleo_models_HEADERS PREFIX Libkleo RELATIVE models ) ecm_generate_headers(libkleo_CamelCase_utils_HEADERS HEADER_NAMES Algorithm Classify CryptoConfig FileSystemWatcher Formatting GnuPG Compat Test REQUIRED_HEADERS libkleo_utils_HEADERS PREFIX Libkleo RELATIVE utils ) ecm_generate_headers(libkleo_CamelCase_ui_HEADERS HEADER_NAMES CryptoConfigModule DNAttributeOrderConfigWidget DirectoryServicesWidget + EditDirectoryServiceDialog FileNameRequester KDHorizontalLine KeyApprovalDialog NewKeyApprovalDialog KeyRequester KeySelectionCombo KeySelectionDialog MessageBox ProgressDialog REQUIRED_HEADERS libkleo_ui_HEADERS PREFIX Libkleo RELATIVE ui ) ecm_generate_pri_file(BASE_NAME Libkleo LIB_NAME KF5Libkleo DEPS "QGpgme" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/Libkleo ) install(FILES ${libkleo_CamelCase_HEADERS} ${libkleo_CamelCase_models_HEADERS} ${libkleo_CamelCase_ui_HEADERS} ${libkleo_CamelCase_utils_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/Libkleo COMPONENT Devel ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kleo_export.h ${libkleo_HEADERS} ${libkleo_models_HEADERS} ${libkleo_ui_HEADERS} ${libkleo_utils_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/libkleo COMPONENT Devel ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) if ( WIN32 ) install ( FILES libkleopatrarc-win32.desktop DESTINATION ${KDE_INSTALL_CONFDIR} RENAME libkleopatrarc ) else () install ( FILES libkleopatrarc.desktop DESTINATION ${KDE_INSTALL_CONFDIR} RENAME libkleopatrarc ) endif () diff --git a/src/ui/editdirectoryservicedialog.cpp b/src/ui/editdirectoryservicedialog.cpp new file mode 100644 index 000000000..9d2c82a45 --- /dev/null +++ b/src/ui/editdirectoryservicedialog.cpp @@ -0,0 +1,392 @@ +/* + 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 "editdirectoryservicedialog.h" + +#include "kleo/keyserverconfig.h" +#include "utils/algorithm.h" + +#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")}; + 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}; + { + 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/editdirectoryservicedialog.h b/src/ui/editdirectoryservicedialog.h new file mode 100644 index 000000000..1092f1984 --- /dev/null +++ b/src/ui/editdirectoryservicedialog.h @@ -0,0 +1,38 @@ +/* + ui/editdirectoryservicedialog.h + + 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 +*/ + +#pragma once + +#include + +#include + +#include "kleo_export.h" + +namespace Kleo +{ +class KeyserverConfig; + +class KLEO_EXPORT EditDirectoryServiceDialog : public QDialog +{ + Q_OBJECT +public: + explicit EditDirectoryServiceDialog(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + ~EditDirectoryServiceDialog() override; + + void setKeyserver(const KeyserverConfig &keyserver); + KeyserverConfig keyserver() const; + +private: + class Private; + const std::unique_ptr d; +}; + +}