diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b01e0b978..806b1bbc8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,239 +1,235 @@ # 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/cryptoconfigmodule.cpp b/src/ui/cryptoconfigmodule.cpp index 6f8f00927..65b30029a 100644 --- a/src/ui/cryptoconfigmodule.cpp +++ b/src/ui/cryptoconfigmodule.cpp @@ -1,932 +1,938 @@ /* cryptoconfigmodule.cpp This file is part of kgpgcertmanager SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include "cryptoconfigmodule.h" #include "cryptoconfigmodule_p.h" #include "directoryserviceswidget.h" #include "kdhorizontalline.h" #include "filenamerequester.h" +#include "kleo/keyserverconfig.h" + #include #include #include #include "kleo_ui_debug.h" #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; return QIcon::fromTheme(ss.replace(QRegExp(QLatin1String("[^a-zA-Z0-9_]")), 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; } return layout == CryptoConfigModule::LinearizedLayout ? KPageView::Plain : layout == CryptoConfigModule::TabbedLayout ? KPageView::Tabbed : /* else */ 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 = QApplication::desktop()->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, 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(); } //// 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, KDHorizontalLine::indentHint()); 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; } const QString title = group->description(); auto hl = new KDHorizontalLine(title.isEmpty() ? *it : title, this); const int row = glay->rowCount(); glay->addWidget(hl, row, 0, 1, 3); mGroupGUIs.append(new CryptoConfigGroupGUI(module, group, glay, this)); } } else if (!groups.empty()) { mGroupGUIs.append(new CryptoConfigGroupGUI(module, mComponent->group(groups.front()), 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, QGridLayout *glay, QWidget *widget) : QObject(module), mGroup(group) { const bool de_vs = Kleo::Formatting::complianceMode() == QLatin1String("de-vs"); const int startRow = glay->rowCount(); const QStringList entries = mGroup->entryList(); for (QStringList::const_iterator it = entries.begin(), end = entries.end(); it != end; ++it) { QGpgME::CryptoConfigEntry *entry = group->entry(*it); Q_ASSERT(entry); /* Skip "dangerous" options if we are running in CO_DE_VS. */ if (de_vs && entry->level() > QGpgME::CryptoConfigEntry::Level_Advanced) { qCDebug(KLEO_UI_LOG) << "entry" << *it << "too advanced, skipping"; continue; } CryptoConfigEntryGUI *entryGUI = CryptoConfigEntryGUIFactory::createEntryGUI(module, entry, *it, 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 } }; 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] = { &_create, // None &_create, // String &_create, // Int &_create, // UInt &_create, // Path nullptr, // Formerly URL nullptr, // LDAPURL &_create, // DirPath }; 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()); } //// static const struct { const char *label; const char *name; } debugLevels[] = { { I18N_NOOP("0 - None"), "none"}, { I18N_NOOP("1 - Basic"), "basic"}, { I18N_NOOP("2 - Verbose"), "advanced"}, { I18N_NOOP("3 - More Verbose"), "expert"}, { I18N_NOOP("4 - All"), "guru"}, }; 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(i18n(debugLevels[i].label)); } if (entry->isReadOnly()) { label->setEnabled(false); mComboBox->setEnabled(false); } else { connect(mComboBox, QOverload::of(&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::of(&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() { // 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 LDAP Servers")); + dialog.setWindowTitle(i18nc("@title:window", "Configure Directory Services")); auto dirserv = new DirectoryServicesWidget(&dialog); prepareURLCfgDialog(&dialog, dirserv, mEntry->isReadOnly()); - dirserv->setX509ReadOnly(mEntry->isReadOnly()); - dirserv->setAllowedSchemes(DirectoryServicesWidget::LDAP); - dirserv->setAllowedProtocols(DirectoryServicesWidget::X509Protocol); - dirserv->addX509Services(mURLList); + 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()) { - setURLList(dirserv->x509Services()); + 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 5314db735..c1d3e949a 100644 --- a/src/ui/directoryserviceswidget.cpp +++ b/src/ui/directoryserviceswidget.cpp @@ -1,713 +1,394 @@ /* - directoryserviceswidget.cpp + ui/directoryserviceswidget.cpp - This file is part of Kleopatra, the KDE keymanager + 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 "directoryserviceswidget.h" -#include "ui_directoryserviceswidget.h" +#include "editdirectoryservicedialog.h" -#include "kleo_ui_debug.h" +#include "kleo/keyserverconfig.h" +#include "utils/gnupg.h" #include -#include -#include -#include -#include - -#include - -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include -#include +#include "kleo_ui_debug.h" using namespace Kleo; namespace { -static QUrl defaultX509Service() -{ - QUrl url; - url.setScheme(QStringLiteral("ldap")); - url.setHost(i18nc("default server name, keep it a valid domain name, ie. no spaces", "server")); - return url; -} - -static bool is_ldap_scheme(const QUrl &url) -{ - const QString scheme = url.scheme(); - return QString::compare(scheme, QStringLiteral("ldap"), Qt::CaseInsensitive) == 0 - || QString::compare(scheme, QStringLiteral("ldaps"), Qt::CaseInsensitive) == 0; -} - -static const struct { - const char label[6]; - unsigned short port; - DirectoryServicesWidget::Scheme base; -} protocols[] = { - { I18N_NOOP("hkp"), 11371, DirectoryServicesWidget::HKP }, - { I18N_NOOP("http"), 80, DirectoryServicesWidget::HTTP }, - { I18N_NOOP("https"), 443, DirectoryServicesWidget::HTTP }, - { I18N_NOOP("ftp"), 21, DirectoryServicesWidget::FTP }, - { I18N_NOOP("ftps"), 990, DirectoryServicesWidget::FTP }, - { I18N_NOOP("ldap"), 389, DirectoryServicesWidget::LDAP }, - { I18N_NOOP("ldaps"), 636, DirectoryServicesWidget::LDAP }, -}; -static const unsigned int numProtocols = sizeof protocols / sizeof * protocols; - -static unsigned short default_port(const QString &scheme) -{ - for (unsigned int i = 0; i < numProtocols; ++i) - if (QString::compare(scheme, QLatin1String(protocols[i].label), Qt::CaseInsensitive) == 0) { - return protocols[i].port; - } - return 0; -} - -static QString display_scheme(const QUrl &url) +bool activeDirectoryIsSupported() { - if (url.scheme().isEmpty()) { - return QStringLiteral("hkp"); - } else { - return url.scheme(); - } + return engineIsVersion(2, 2, 28, GpgME::GpgSMEngine); } -static QString display_host(const QUrl &url) +bool isStandardActiveDirectory(const KeyserverConfig &keyserver) { - // work around "subkeys.pgp.net" being interpreted as a path, not host - if (url.host().isEmpty()) { - return url.path(); - } else { - return url.host(); - } -} - -static unsigned short display_port(const QUrl &url) -{ - if (url.port() > 0) { - return url.port(); - } else { - return default_port(display_scheme(url)); - } + return (keyserver.authentication() == KeyserverAuthentication::ActiveDirectory) + && keyserver.host().isEmpty(); } -static QRect calculate_geometry(const QRect &cell, const QSize &sizeHint) +bool keyserverIsEditable(const KeyserverConfig &keyserver) { - const int height = qMax(cell.height(), sizeHint.height()); - return {cell.left(), cell.top() - (height - cell.height()) / 2, - cell.width(), height}; + // standard AD is not editable + return !isStandardActiveDirectory(keyserver); } -/* The Model contains a bit historic cruft because in the past it was - * thought to be a good idea to combine openPGP and X509 in a single - * table although while you can have multiple X509 Keyservers there can - * only be one OpenPGP Keyserver. So the OpenPGP Keyserver is now a - * single lineedit. */ -class Model : public QAbstractTableModel +class KeyserverModel : public QAbstractListModel { Q_OBJECT public: - explicit Model(QObject *parent = nullptr) - : QAbstractTableModel(parent), - m_items(), - m_x509ReadOnly(false), - m_schemes(DirectoryServicesWidget::LDAP) + explicit KeyserverModel(QObject *parent = nullptr) + : QAbstractListModel{parent} { - } - void setX509ReadOnly(bool ro) + void setKeyservers(const std::vector &servers) { - if (ro == m_x509ReadOnly) { - return; - } - m_x509ReadOnly = ro; - for (int row = 0, end = rowCount(); row != end; ++row) { - Q_EMIT dataChanged(index(row, 0), index(row, NumColumns)); - } + clear(); + beginInsertRows(QModelIndex(), 0, servers.size() - 1); + m_items = servers; + endInsertRows(); } - QModelIndex addX509Service(const QUrl &url, bool force = false) + void addKeyserver(const KeyserverConfig &keyserver) { - const auto it = force ? m_items.end() : findExistingUrl(url); - unsigned int row; - if (it != m_items.end()) { - // existing item: - row = it - m_items.begin(); - Q_EMIT dataChanged(index(row, 0), index(row, NumColumns)); - } else { - // append new item - row = m_items.size(); - beginInsertRows(QModelIndex(), row, row); - m_items.push_back(url); - endInsertRows(); - } - return index(row, firstEditableColumn(row)); + const auto row = m_items.size(); + beginInsertRows(QModelIndex(), row, row); + m_items.push_back(keyserver); + endInsertRows(); } - unsigned int numServices() const - { - return m_items.size(); - } - QUrl service(unsigned int row) const + KeyserverConfig getKeyserver(unsigned int id) { - return row < m_items.size() ? m_items[row] : QUrl(); - } - - enum Columns { - Host, - Port, - BaseDN, - UserName, - Password, - Flags, + if (id >= m_items.size()) { + qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id; + return {}; + } - NumColumns, - }; + return m_items[id]; + } - QModelIndex duplicateRow(unsigned int row) + void updateKeyserver(unsigned int id, const KeyserverConfig &keyserver) { - if (row >= m_items.size()) { - return {}; + if (id >= m_items.size()) { + qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id; + return; } - beginInsertRows(QModelIndex(), row + 1, row + 1); - m_items.insert(m_items.begin() + row + 1, m_items[row]); - endInsertRows(); - return index(row + 1, 0); + m_items[id] = keyserver; + Q_EMIT dataChanged(index(id), index(id)); } - void deleteRow(unsigned int row) + void deleteKeyserver(unsigned int id) { - if (row >= m_items.size()) { + if (id >= m_items.size()) { + qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id; return; } - beginRemoveRows(QModelIndex(), row, row); - m_items.erase(m_items.begin() + row); - endInsertRows(); + 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 columnCount(const QModelIndex & = QModelIndex()) const override - { - return NumColumns; - } int rowCount(const QModelIndex & = QModelIndex()) const override { return m_items.size(); } - QVariant data(const QModelIndex &idx, int role) const override; - QVariant headerData(int section, Qt::Orientation o, int role) const override; - - Qt::ItemFlags flags(const QModelIndex &idx) const override; - bool setData(const QModelIndex &idx, const QVariant &value, int role) override; - -private: - bool doSetData(unsigned int row, unsigned int column, const QVariant &value, int role); + 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 {}; + } - static QString toolTipForColumn(int column); - bool isLdapRow(unsigned int row) const; - int firstEditableColumn(unsigned int) const + bool hasActiveDirectory() { - return Host; + // 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: - std::vector m_items; - bool m_x509ReadOnly : 1; - DirectoryServicesWidget::Schemes m_schemes; + using QAbstractListModel::setData; private: - std::vector::iterator findExistingUrl(const QUrl &url) - { - return std::find_if(m_items.begin(), m_items.end(), - [&url](const QUrl &item) { - const QUrl &lhs = url; - const QUrl &rhs = item; - return QString::compare(display_scheme(lhs), display_scheme(rhs), Qt::CaseInsensitive) == 0 - && QString::compare(display_host(lhs), display_host(rhs), Qt::CaseInsensitive) == 0 - && lhs.port() == rhs.port() - && lhs.userName() == rhs.userName() - // ... ignore password... - && (!is_ldap_scheme(lhs) - || lhs.query() == rhs.query()); - }); - } + std::vector m_items; }; +} -class Delegate : public QItemDelegate +class DirectoryServicesWidget::Private { - Q_OBJECT -public: - explicit Delegate(QObject *parent = nullptr) - : QItemDelegate(parent), - m_schemes(DirectoryServicesWidget::LDAP) - { + 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; - void setAllowedSchemes(const DirectoryServicesWidget::Schemes schemes) +public: + Private(DirectoryServicesWidget *qq) + : q(qq) { - m_schemes = schemes; - } - DirectoryServicesWidget::Schemes allowedSchemes() const + 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) { editKeyserver(index); }); + connect(ui.editButton, &QPushButton::clicked, q, [this] () { editKeyserver(); }); + connect(ui.deleteButton, &QPushButton::clicked, q, [this] () { deleteKeyserver(); }); + } + + void setReadOnly(bool ro) { - return m_schemes; + readOnly = ro; + updateActions(); } - QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &idx) const override + void setKeyservers(const std::vector &servers) { - switch (idx.column()) { - case Model::Port: - return createPortWidget(parent); - } - return QItemDelegate::createEditor(parent, option, idx); + keyserverModel->setKeyservers(servers); } - void setEditorData(QWidget *editor, const QModelIndex &idx) const override + std::vector keyservers() const { - switch (idx.column()) { - case Model::Port: - setPortEditorData(qobject_cast(editor), idx.data(Qt::EditRole).toInt()); - break; - default: - QItemDelegate::setEditorData(editor, idx); - break; + std::vector result; + result.reserve(keyserverModel->rowCount()); + for (int row = 0; row < keyserverModel->rowCount(); ++row) { + result.push_back(keyserverModel->getKeyserver(row)); } + return result; } - void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &idx) const override - { - switch (idx.column()) { - case Model::Port: - setPortModelData(qobject_cast(editor), model, idx); - break; - default: - QItemDelegate::setModelData(editor, model, idx); - break; - } - } - - void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override + void clear() { - if (index.column() == Model::Port) { - editor->setGeometry(calculate_geometry(option.rect, editor->sizeHint())); - } else { - QItemDelegate::updateEditorGeometry(editor, option, index); + if (keyserverModel->rowCount() == 0) { + return; } + keyserverModel->clear(); } private: - QWidget *createPortWidget(QWidget *parent) const + auto selectedIndex() { - auto sb = new QSpinBox(parent); - sb->setRange(1, USHRT_MAX); // valid port numbers - return sb; + const auto indexes = ui.keyserverList->selectionModel()->selectedRows(); + return indexes.empty() ? QModelIndex() : indexes[0]; } - void setPortEditorData(QSpinBox *sb, unsigned short port) const - { - Q_ASSERT(sb); - sb->setValue(port); - } - void setPortModelData(const QSpinBox *sb, QAbstractItemModel *model, const QModelIndex &idx) const - { - Q_ASSERT(sb); - Q_ASSERT(model); - model->setData(idx, sb->value()); - } - -private: - DirectoryServicesWidget::Schemes m_schemes; -}; -} - -class DirectoryServicesWidget::Private -{ - friend class ::Kleo::DirectoryServicesWidget; - DirectoryServicesWidget *const q; -public: - explicit Private(DirectoryServicesWidget *qq) - : q(qq), - protocols(AllProtocols), - readOnlyProtocols(NoProtocol), - model(), - delegate(), - ui(q) + void modelChanged() { - ui.treeView->setModel(&model); - ui.treeView->setItemDelegate(&delegate); - - connect(&model, &QAbstractItemModel::dataChanged, q, &DirectoryServicesWidget::changed); - connect(&model, &QAbstractItemModel::rowsInserted, q, &DirectoryServicesWidget::changed); - connect(&model, &QAbstractItemModel::rowsRemoved, q, &DirectoryServicesWidget::changed); - connect(ui.treeView->selectionModel(), &QItemSelectionModel::selectionChanged, - q, [this]() { slotSelectionChanged(); }); - - slotShowUserAndPasswordToggled(false); + updateActions(); + Q_EMIT q->changed(); } -private: - void edit(const QModelIndex &index) - { - if (index.isValid()) { - ui.treeView->clearSelection(); - ui.treeView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); - ui.treeView->edit(index); - } - } - void slotNewX509Clicked() - { - edit(model.addX509Service(defaultX509Service(), true)); - } - void slotDeleteClicked() + void selectionChanged() { - model.deleteRow(selectedRow()); - } - void slotSelectionChanged() - { - enableDisableActions(); - } - void slotShowUserAndPasswordToggled(bool on) - { - QHeaderView *const hv = ui.treeView->header(); - Q_ASSERT(hv); - hv->setSectionHidden(Model::UserName, !on); - hv->setSectionHidden(Model::Password, !on); + updateActions(); } - int selectedRow() const - { - const QModelIndexList mil = ui.treeView->selectionModel()->selectedRows(); - return mil.empty() ? -1 : mil.front().row(); - } - int currentRow() const + void updateActions() { - const QModelIndex idx = ui.treeView->selectionModel()->currentIndex(); - return idx.isValid() ? idx.row() : -1; + 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 enableDisableActions() + void handleEditKeyserverDialogResult(const int id, const EditDirectoryServiceDialog *dialog) { - const bool x509 = (protocols & X509Protocol) && !(readOnlyProtocols & X509Protocol); - ui.newTB->setEnabled(x509); - const int row = selectedRow(); - ui.deleteTB->setEnabled(row >= 0 && !(readOnlyProtocols & X509Protocol)); - } - -private: - Protocols protocols; - Protocols readOnlyProtocols; - Model model; - Delegate delegate; - struct UI : Ui_DirectoryServicesWidget { - - explicit UI(DirectoryServicesWidget *q) - : Ui_DirectoryServicesWidget() - { - setupUi(q); + if (id >= 0) { + keyserverModel->updateKeyserver(id, dialog->keyserver()); + } else { + keyserverModel->addKeyserver(dialog->keyserver()); } + } - } ui; -}; - -DirectoryServicesWidget::DirectoryServicesWidget(QWidget *p, Qt::WindowFlags f) - : QWidget(p, f), d(new Private(this)) -{ - -} - -DirectoryServicesWidget::~DirectoryServicesWidget() -{ - delete d; -} - -void DirectoryServicesWidget::setAllowedSchemes(Schemes schemes) -{ - d->delegate.setAllowedSchemes(schemes); -} + 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); -DirectoryServicesWidget::Schemes DirectoryServicesWidget::allowedSchemes() const -{ - return d->delegate.allowedSchemes(); -} + connect(dialog, &QDialog::accepted, q, [dialog, id, this] { + handleEditKeyserverDialogResult(id, dialog); + }); -void DirectoryServicesWidget::setAllowedProtocols(Protocols protocols) -{ - if (d->protocols == protocols) { - return; + dialog->show(); } - d->protocols = protocols; - d->enableDisableActions(); -} -DirectoryServicesWidget::Protocols DirectoryServicesWidget::allowedProtocols() const -{ - return d->protocols; -} - -void DirectoryServicesWidget::setReadOnlyProtocols(Protocols protocols) -{ - if (d->readOnlyProtocols == protocols) { - return; + void addActiveDirectory() + { + KeyserverConfig keyserver; + keyserver.setAuthentication(KeyserverAuthentication::ActiveDirectory); + keyserverModel->addKeyserver(keyserver); } - d->readOnlyProtocols = protocols; - d->model.setX509ReadOnly(protocols & X509Protocol); - d->enableDisableActions(); -} - -DirectoryServicesWidget::Protocols DirectoryServicesWidget::readOnlyProtocols() const -{ - return d->readOnlyProtocols; -} -void DirectoryServicesWidget::addX509Services(const QList &urls) -{ - for (const QUrl &url : urls) { - d->model.addX509Service(url); + void addLdapServer() + { + showEditKeyserverDialog(-1, {}, i18nc("@title:window", "LDAP Directory Service")); } -} -QList DirectoryServicesWidget::x509Services() const -{ - QList result; - unsigned int numServices{d->model.numServices()}; - result.reserve(numServices); - for (unsigned int i = 0; i != numServices; ++i) { - result.push_back(d->model.service(i)); - } - return result; -} + 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; + } -void DirectoryServicesWidget::clear() -{ - if (!d->model.numServices()) { - return; + showEditKeyserverDialog(id, keyserver, i18nc("@title:window", "LDAP Directory Service")); } - d->model.clear(); - Q_EMIT changed(); -} -// -// Model -// - -QVariant Model::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Horizontal) - if (role == Qt::ToolTipRole) { - return toolTipForColumn(section); - } else if (role == Qt::DisplayRole) - switch (section) { - case Host: return i18n("Server Name"); - case Port: return i18n("Server Port"); - case BaseDN: return i18n("Base DN"); - case UserName: return i18n("User Name"); - case Password: return i18n("Password"); - case Flags: return i18nc("Flags to enable/disable certain features", "Flags"); - default: return QVariant(); - } - else { - return QVariant(); + void deleteKeyserver() + { + const QModelIndex serverIndex = selectedIndex(); + if (!serverIndex.isValid()) { + qCDebug(KLEO_UI_LOG) << __func__ << "selection is empty"; + return; } - else { - return QAbstractTableModel::headerData(section, orientation, role); + keyserverModel->deleteKeyserver(serverIndex.row()); } -} +}; -QVariant Model::data(const QModelIndex &index, int role) const +DirectoryServicesWidget::DirectoryServicesWidget(QWidget *parent) + : QWidget{parent} + , d{std::make_unique(this)} { - const unsigned int row = index.row(); - if (index.isValid() && row < m_items.size()) - switch (role) { - case Qt::ToolTipRole: { - const QString tt = toolTipForColumn(index.column()); - if (!m_x509ReadOnly) { - return tt; - } else - return tt.isEmpty() - ? i18n("(read-only)") - : i18nc("amended tooltip; %1: original tooltip", - "%1 (read-only)", tt); - } - case Qt::DisplayRole: - case Qt::EditRole: - switch (index.column()) { - case Host: - return display_host(m_items[row]); - case Port: - return display_port(m_items[row]); - case BaseDN: - if (isLdapRow(row)) { - return m_items[row].query(); - } else { - return QVariant(); - } - case UserName: - return m_items[row].userName(); - case Password: - return m_items[row].password(); - case Flags: - return m_items[row].fragment(); - default: - return QVariant(); - } - } - return QVariant(); } -bool Model::isLdapRow(unsigned int row) const -{ - if (row >= m_items.size()) { - return false; - } - return is_ldap_scheme(m_items[row]); -} +DirectoryServicesWidget::~DirectoryServicesWidget() = default; -Qt::ItemFlags Model::flags(const QModelIndex &index) const +void DirectoryServicesWidget::setKeyservers(const std::vector &servers) { - const unsigned int row = index.row(); - Qt::ItemFlags flags = QAbstractTableModel::flags(index); - if (m_x509ReadOnly) { - flags &= ~Qt::ItemIsSelectable; - } - if (index.isValid() && row < m_items.size()) - switch (index.column()) { - case Host: - case Port: - if (m_x509ReadOnly) { - return flags & ~(Qt::ItemIsEditable | Qt::ItemIsEnabled); - } else { - return flags | Qt::ItemIsEditable; - } - case BaseDN: - if (isLdapRow(row) && !m_x509ReadOnly) { - return flags | Qt::ItemIsEditable; - } else { - return flags & ~(Qt::ItemIsEditable | Qt::ItemIsEnabled); - } - case UserName: - case Password: - if (m_x509ReadOnly) { - return flags & ~(Qt::ItemIsEditable | Qt::ItemIsEnabled); - } else { - return flags | Qt::ItemIsEditable; - } - case Flags: - if (m_x509ReadOnly) { - return flags & ~(Qt::ItemIsEditable | Qt::ItemIsEnabled); - } else { - return flags | Qt::ItemIsEditable; - } - } - - return flags; + d->setKeyservers(servers); } -bool Model::setData(const QModelIndex &idx, const QVariant &value, int role) +std::vector DirectoryServicesWidget::keyservers() const { - const unsigned int row = idx.row(); - if (!idx.isValid() || row >= m_items.size()) { - return false; - } - if (m_x509ReadOnly) { - return false; - } - if (!doSetData(row, idx.column(), value, role)) { - return false; - } - Q_EMIT dataChanged(idx, idx); - return true; + return d->keyservers(); } -bool Model::doSetData(unsigned int row, unsigned int column, const QVariant &value, int role) +void DirectoryServicesWidget::setReadOnly(bool readOnly) { - if (role == Qt::EditRole) - switch (column) { - case Host: - if (display_host(m_items[row]) != m_items[row].host()) { - m_items[row].setScheme(display_scheme(m_items[row])); - } - m_items[row].setHost(value.toString()); - return true; - case Port: - if (value.toUInt() == default_port(display_scheme(m_items[row]))) { - m_items[row].setPort(-1); - } else { - m_items[row].setPort(value.toUInt()); - } - return true; - case BaseDN: - if (value.toString().isEmpty()) { - m_items[row].setPath(QString()); - m_items[row].setQuery(QString()); - } else { - m_items[row].setQuery(value.toString()); - } - return true; - case UserName: - m_items[row].setUserName(value.toString()); - return true; - case Password: - m_items[row].setPassword(value.toString()); - return true; - case Flags: - if (value.toString().isEmpty()) { - m_items[row].setFragment(QString()); // unset the fragment - } else { - m_items[row].setFragment(value.toString()); - } - return true; - } - return false; + d->setReadOnly(readOnly); } - -// static -QString Model::toolTipForColumn(int column) +void DirectoryServicesWidget::clear() { - switch (column) { - case Host: return i18n("Enter the name or IP address of the server " - "hosting the directory service."); - case Port: return i18n("(Optional, the default is fine in most cases) " - "Pick the port number the directory service is " - "listening on."); - case BaseDN: return i18n("(Only for LDAP) " - "Enter the base DN for this LDAP server to " - "limit searches to only that subtree of the directory."); - case UserName: return i18n("(Optional) " - "Enter your user name here, if needed."); - case Password: return i18n("(Optional, not recommended) " - "Enter your password here, if needed. " - "Note that the password will be saved in the clear " - "in a config file in your home directory."); - case Flags: return i18n("(Optional) " - "Enter 'ldaps' to specify that a TLS connection shall be used."); - default: - return QString(); - } + d->clear(); } #include "directoryserviceswidget.moc" -#include "moc_directoryserviceswidget.cpp" diff --git a/src/ui/directoryserviceswidget.h b/src/ui/directoryserviceswidget.h index f13baaac3..1e190220d 100644 --- a/src/ui/directoryserviceswidget.h +++ b/src/ui/directoryserviceswidget.h @@ -1,95 +1,48 @@ /* - directoryserviceswidget.h + ui/directoryserviceswidget.h - This file is part of Kleopatra, the KDE keymanager + This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2001, 2002, 2004 Klarälvdalens Datakonsult AB + SPDX-FileCopyrightText: 2021 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" -#include + #include +#include +#include + namespace Kleo { +class KeyserverConfig; class KLEO_EXPORT DirectoryServicesWidget : public QWidget { Q_OBJECT public: - explicit DirectoryServicesWidget(QWidget *parent = nullptr, Qt::WindowFlags f = {}); - ~DirectoryServicesWidget(); - - enum Scheme { - NoScheme = 0, - HKP = 1, - HTTP = 2, - FTP = 4, - LDAP = 8, - - AllSchemes = HKP | HTTP | FTP | LDAP - }; - Q_DECLARE_FLAGS(Schemes, Scheme) - - enum Protocol { - NoProtocol = 0, - X509Protocol = 1, - - AllProtocols = X509Protocol - }; - Q_DECLARE_FLAGS(Protocols, Protocol) - - void setAllowedSchemes(Schemes schemes); - Schemes allowedSchemes() const; - - void setAllowedProtocols(Protocols protocols); - Protocols allowedProtocols() const; - - void setX509Allowed(bool allowed); + explicit DirectoryServicesWidget(QWidget *parent = nullptr); + ~DirectoryServicesWidget() override; - void setReadOnlyProtocols(Protocols protocols); - Protocols readOnlyProtocols() const; + void setKeyservers(const std::vector &keyservers); + std::vector keyservers() const; - void setX509ReadOnly(bool ro); - - void addX509Services(const QList &urls); - QList x509Services() const; + void setReadOnly(bool readOnly); public Q_SLOTS: void clear(); Q_SIGNALS: void changed(); private: class Private; - Private *const d; - Q_PRIVATE_SLOT(d, void slotNewX509Clicked()) - Q_PRIVATE_SLOT(d, void slotDeleteClicked()) - Q_PRIVATE_SLOT(d, void slotSelectionChanged()) - Q_PRIVATE_SLOT(d, void slotShowUserAndPasswordToggled(bool)) + const std::unique_ptr d; }; } - -inline void Kleo::DirectoryServicesWidget::setX509Allowed(bool allowed) -{ - if (allowed) { - setAllowedProtocols(allowedProtocols() | X509Protocol); - } else { - setAllowedProtocols(allowedProtocols() & ~X509Protocol); - } -} - -inline void Kleo::DirectoryServicesWidget::setX509ReadOnly(bool ro) -{ - if (ro) { - setReadOnlyProtocols(readOnlyProtocols() | X509Protocol); - } else { - setReadOnlyProtocols(readOnlyProtocols() & ~X509Protocol); - } -} - diff --git a/src/ui/directoryserviceswidget.ui b/src/ui/directoryserviceswidget.ui deleted file mode 100644 index 4143f70bf..000000000 --- a/src/ui/directoryserviceswidget.ui +++ /dev/null @@ -1,202 +0,0 @@ - - - DirectoryServicesWidget - - - - 0 - 0 - 345 - 363 - - - - Directory Services Configuration - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - X509 Directory services: - - - - - - - - This is a list of all directory services that are configured for use with X.509. - - - false - - - false - - - - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Click to add a service - - - Click this button to create a new directory service entry as a clone of the currently selected one (or with default values, if no other is selected). You can then configure details in the table on the left hand. - - - New - - - - - - - false - - - - 0 - 0 - - - - Click to remove the currently selected service - - - Click this button to remove the currently selected directory service. The change will only take effect once you acknowledge the main configuration dialog. - - - Delete - - - Qt::ToolButtonTextBesideIcon - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 51 - - - - - - - - - - - - Use this option to switch display of username and password information on or off in the above table. - - - Show user and password information - - - - - - - - - - - - newTB - clicked() - DirectoryServicesWidget - slotNewX509Clicked() - - - 535 - 57 - - - 571 - 55 - - - - - deleteTB - clicked() - DirectoryServicesWidget - slotDeleteClicked() - - - 537 - 95 - - - 575 - 95 - - - - - showUserAndPasswordCB - toggled(bool) - DirectoryServicesWidget - slotShowUserAndPasswordToggled(bool) - - - 314 - 341 - - - 342 - 357 - - - - - - slotNewX509Clicked() - slotDeleteClicked() - slotShowUserAndPasswordToggled(bool) - -