diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d6b9ee..152344e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,105 +1,111 @@ cmake_minimum_required(VERSION 3.5) -set(PIM_VERSION "5.14.40") +set(PIM_VERSION "5.14.41") project(KLdap VERSION ${PIM_VERSION}) # ECM setup set(KF5_MIN_VERSION "5.70.0") find_package(ECM ${KF5_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${KLdap_SOURCE_DIR}/cmake ${ECM_MODULE_PATH}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(GenerateExportHeader) include(ECMGenerateHeaders) include(ECMGeneratePriFile) include(ECMSetupVersion) include(FeatureSummary) include(ECMQtDeclareLoggingCategory) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) set(KLDAP_LIB_VERSION ${PIM_VERSION}) find_package(KF5KIO ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5I18n ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5DocTools ${KF5_MIN_VERSION} CONFIG) +find_package(KF5Wallet ${KF5_MIN_VERSION} CONFIG REQUIRED) +find_package(KF5KIO ${KF5_MIN_VERSION} CONFIG REQUIRED) + # tell what is missing without doctools set_package_properties(KF5DocTools PROPERTIES DESCRIPTION "Provides tools to generate documentation in various format from DocBook files" TYPE OPTIONAL PURPOSE "Required to build documentation") ecm_setup_version(PROJECT VARIABLE_PREFIX KLDAP VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kldap_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5LdapConfigVersion.cmake" SOVERSION 5 ) ########### Find packages ########### find_package(KF5WidgetsAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) add_definitions(-DQT_NO_FOREACH) add_definitions(-DQT_NO_KEYWORDS) if (EXISTS "${CMAKE_SOURCE_DIR}/.git") add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050f00) add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054700) endif() find_package(Ldap) set_package_properties(Ldap PROPERTIES TYPE RECOMMENDED PURPOSE "Needed to provide LDAP functionality in KDE" ) find_package(Sasl2) set_package_properties(Sasl2 PROPERTIES TYPE REQUIRED) if (Ldap_FOUND) set(LDAP_FOUND 1) endif() add_definitions(-DTRANSLATION_DOMAIN=\"libkldap5\") ########### CMake Config Files ########### set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5Ldap") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5LdapConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5LdapConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5LdapConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5LdapConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5LdapTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5LdapTargets.cmake NAMESPACE KF5::) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kldap_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) ########### Targets ########### add_subdirectory(src) add_subdirectory(kioslave) if(BUILD_TESTING) add_subdirectory(autotests) + add_subdirectory(tests) endif() ecm_qt_install_logging_categories( EXPORT KLDAP FILE kldap.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 663401e..b06f5be 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,114 +1,155 @@ include(CheckFunctionExists) include(CheckIncludeFiles) include(CheckSymbolExists) check_include_files(sys/time.h HAVE_SYS_TIME_H) set(kldap_EXTRA_LIBS) if(Ldap_FOUND) set(kldap_EXTRA_LIBS Ldap::Ldap) if(WIN32) set(kldap_EXTRA_LIBS ${kldap_EXTRA_LIBS} ws2_32) endif() set(HAVE_LDAP_H) set(CMAKE_REQUIRED_INCLUDES lber.h ldap.h) set(CMAKE_REQUIRED_LIBRARIES Ldap::Ldap) check_function_exists(ldap_start_tls_s HAVE_LDAP_START_TLS_S) check_function_exists(ldap_initialize HAVE_LDAP_INITIALIZE) check_function_exists(ber_memfree HAVE_BER_MEMFREE) check_function_exists(ldap_unbind_ext HAVE_LDAP_UNBIND_EXT) check_function_exists(ldap_extended_operation HAVE_LDAP_EXTENDED_OPERATION) check_function_exists(ldap_extended_operation_s HAVE_LDAP_EXTENDED_OPERATION_S) check_symbol_exists(ldap_extended_operation ldap.h HAVE_LDAP_EXTENDED_OPERATION_PROTOTYPE) check_symbol_exists(ldap_extended_operation_s ldap.h HAVE_LDAP_EXTENDED_OPERATION_S_PROTOTYPE) endif() set(kldap_EXTRA_LIBS ${kldap_EXTRA_LIBS} Sasl2::Sasl2) configure_file(kldap_config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/kldap_config.h) ########### next target ############### -set(kldap_LIB_SRCS - ber.cpp - ldif.cpp - ldapurl.cpp - ldapserver.cpp - ldapobject.cpp - ldapconnection.cpp - ldapoperation.cpp - ldapcontrol.cpp - ldapsearch.cpp - ldapconfigwidget.cpp - ldapdn.cpp +set(kldap_LIB_core_SRCS + core/ber.cpp + core/ldif.cpp + core/ldapurl.cpp + core/ldapserver.cpp + core/ldapobject.cpp + core/ldapconnection.cpp + core/ldapoperation.cpp + core/ldapcontrol.cpp + core/ldapsearch.cpp + core/ldapdn.cpp ) + +set(kldap_LIB_widgets_SRCS + widgets/ldapconfigwidget.cpp + widgets/addhostdialog.cpp + widgets/ldapclient.cpp + widgets/ldapclientsearch.cpp + widgets/ldapclientsearchconfig.cpp + widgets/ldapconfigurewidget.cpp + #widgets/ldapsearchdialog.cpp + ) + + set(kldap_LIB_SRCS + ${kldap_LIB_core_SRCS} + ${kldap_LIB_widgets_SRCS} + ) + ecm_qt_declare_logging_category(kldap_LIB_SRCS HEADER ldap_debug.h IDENTIFIER LDAP_LOG CATEGORY_NAME org.kde.pim.ldap DESCRIPTION "kldaplib (kldap)" OLD_CATEGORY_NAMES log_ldap EXPORT KLDAP ) +ecm_qt_declare_logging_category(kldap_LIB_SRCS HEADER ldapclient_debug.h IDENTIFIER LDAPCLIENT_LOG CATEGORY_NAME org.kde.pim.ldapclient + DESCRIPTION "ldapclient (libkdepim)" + OLD_CATEGORY_NAMES log_ldapclient + EXPORT KLDAP + ) + add_library(KF5Ldap ${kldap_LIB_SRCS}) generate_export_header(KF5Ldap BASE_NAME kldap) add_library(KF5::Ldap ALIAS KF5Ldap) target_link_libraries(KF5Ldap PRIVATE Qt5::Widgets KF5::I18n KF5::WidgetsAddons + KF5::ConfigCore + KF5::Wallet + KF5::CoreAddons + KF5::KIOWidgets ${kldap_EXTRA_LIBS} ) target_include_directories(KF5Ldap INTERFACE "$") -target_include_directories(KF5Ldap PUBLIC "$") +target_include_directories(KF5Ldap PUBLIC "$") set_target_properties(KF5Ldap PROPERTIES VERSION ${KLDAP_VERSION_STRING} SOVERSION ${KLDAP_SOVERSION} EXPORT_NAME Ldap ) install(TARGETS KF5Ldap EXPORT KF5LdapTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### -ecm_generate_headers(KLdap_CamelCase_HEADERS +ecm_generate_headers(KLdapCore_CamelCase_HEADERS HEADER_NAMES Ber - LdapConfigWidget LdapConnection LdapControl LdapDN LdapObject LdapOperation LdapSearch LdapServer LdapDefs LdapUrl Ldif + RELATIVE core + PREFIX KLDAP + REQUIRED_HEADERS KLdapCore_HEADERS +) + +ecm_generate_headers(KLdapWidgets_CamelCase_HEADERS + HEADER_NAMES + LdapConfigWidget + LdapClientSearchConfig + LdapClientSearch + AddHostDialog + LdapSearchDialog + LdapClient + LdapConfigureWidget + RELATIVE widgets PREFIX KLDAP - REQUIRED_HEADERS KLdap_HEADERS + REQUIRED_HEADERS KLdapWidgets_HEADERS ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kldap_export.h - ${KLdap_HEADERS} + ${KLdapCore_HEADERS} + ${KLdapWidgets_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KLDAP/kldap COMPONENT Devel ) install(FILES - ${KLdap_CamelCase_HEADERS} + ${KLdapCore_CamelCase_HEADERS} + ${KLdapWidgets_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KLDAP/KLDAP/ COMPONENT Devel ) ecm_generate_pri_file(BASE_NAME Ldap LIB_NAME KF5Ldap FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KLDAP/) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/Messages.sh b/src/Messages.sh index 18480f6..f415639 100644 --- a/src/Messages.sh +++ b/src/Messages.sh @@ -1,3 +1,3 @@ #! /bin/sh -$XGETTEXT *.cpp -o $podir/libkldap5.pot +$XGETTEXT core/*.cpp widgets/*.cpp -o $podir/libkldap5.pot diff --git a/src/ber.cpp b/src/core/ber.cpp similarity index 100% rename from src/ber.cpp rename to src/core/ber.cpp diff --git a/src/ber.h b/src/core/ber.h similarity index 100% rename from src/ber.h rename to src/core/ber.h diff --git a/src/ldapconnection.cpp b/src/core/ldapconnection.cpp similarity index 100% rename from src/ldapconnection.cpp rename to src/core/ldapconnection.cpp diff --git a/src/ldapconnection.h b/src/core/ldapconnection.h similarity index 100% rename from src/ldapconnection.h rename to src/core/ldapconnection.h diff --git a/src/ldapcontrol.cpp b/src/core/ldapcontrol.cpp similarity index 100% rename from src/ldapcontrol.cpp rename to src/core/ldapcontrol.cpp diff --git a/src/ldapcontrol.h b/src/core/ldapcontrol.h similarity index 100% rename from src/ldapcontrol.h rename to src/core/ldapcontrol.h diff --git a/src/ldapdefs.h b/src/core/ldapdefs.h similarity index 100% rename from src/ldapdefs.h rename to src/core/ldapdefs.h diff --git a/src/ldapdn.cpp b/src/core/ldapdn.cpp similarity index 100% rename from src/ldapdn.cpp rename to src/core/ldapdn.cpp diff --git a/src/ldapdn.h b/src/core/ldapdn.h similarity index 100% rename from src/ldapdn.h rename to src/core/ldapdn.h diff --git a/src/ldapobject.cpp b/src/core/ldapobject.cpp similarity index 100% rename from src/ldapobject.cpp rename to src/core/ldapobject.cpp diff --git a/src/ldapobject.h b/src/core/ldapobject.h similarity index 100% rename from src/ldapobject.h rename to src/core/ldapobject.h diff --git a/src/ldapoperation.cpp b/src/core/ldapoperation.cpp similarity index 100% rename from src/ldapoperation.cpp rename to src/core/ldapoperation.cpp diff --git a/src/ldapoperation.h b/src/core/ldapoperation.h similarity index 100% rename from src/ldapoperation.h rename to src/core/ldapoperation.h diff --git a/src/ldapsearch.cpp b/src/core/ldapsearch.cpp similarity index 100% rename from src/ldapsearch.cpp rename to src/core/ldapsearch.cpp diff --git a/src/ldapsearch.h b/src/core/ldapsearch.h similarity index 100% rename from src/ldapsearch.h rename to src/core/ldapsearch.h diff --git a/src/ldapserver.cpp b/src/core/ldapserver.cpp similarity index 100% rename from src/ldapserver.cpp rename to src/core/ldapserver.cpp diff --git a/src/ldapserver.h b/src/core/ldapserver.h similarity index 100% rename from src/ldapserver.h rename to src/core/ldapserver.h diff --git a/src/ldapurl.cpp b/src/core/ldapurl.cpp similarity index 100% rename from src/ldapurl.cpp rename to src/core/ldapurl.cpp diff --git a/src/ldapurl.h b/src/core/ldapurl.h similarity index 100% rename from src/ldapurl.h rename to src/core/ldapurl.h diff --git a/src/ldif.cpp b/src/core/ldif.cpp similarity index 100% rename from src/ldif.cpp rename to src/core/ldif.cpp diff --git a/src/ldif.h b/src/core/ldif.h similarity index 100% rename from src/ldif.h rename to src/core/ldif.h diff --git a/src/w32-ldap-help.h b/src/core/w32-ldap-help.h similarity index 100% rename from src/w32-ldap-help.h rename to src/core/w32-ldap-help.h diff --git a/src/widgets/addhostdialog.cpp b/src/widgets/addhostdialog.cpp new file mode 100644 index 0000000..31ab346 --- /dev/null +++ b/src/widgets/addhostdialog.cpp @@ -0,0 +1,201 @@ +/* + This file is part of libkldap. + + Copyright (c) 2002-2010 Tobias Koenig + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "addhostdialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace KLDAP; +class KLDAP::AddHostDialogPrivate +{ +public: + AddHostDialogPrivate(AddHostDialog *qq) + : q(qq) + { + } + + ~AddHostDialogPrivate() + { + writeConfig(); + } + + void readConfig(); + void writeConfig(); + KLDAP::LdapConfigWidget *mCfg = nullptr; + KLDAP::LdapServer *mServer = nullptr; + QPushButton *mOkButton = nullptr; + AddHostDialog *q = nullptr; +}; + +void AddHostDialogPrivate::readConfig() +{ + KConfigGroup group(KSharedConfig::openConfig(), "AddHostDialog"); + const QSize size = group.readEntry("Size", QSize(600, 400)); + if (size.isValid()) { + q->resize(size); + } +} + +void AddHostDialogPrivate::writeConfig() +{ + KConfigGroup group(KSharedConfig::openConfig(), "AddHostDialog"); + group.writeEntry("Size", q->size()); + group.sync(); +} + +AddHostDialog::AddHostDialog(KLDAP::LdapServer *server, QWidget *parent) + : QDialog(parent) + , d(new KLDAP::AddHostDialogPrivate(this)) +{ + setWindowTitle(i18nc("@title:window", "Add Host")); + QVBoxLayout *mainLayout = new QVBoxLayout(this); + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + d->mOkButton = buttonBox->button(QDialogButtonBox::Ok); + d->mOkButton->setDefault(true); + d->mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return); + connect(buttonBox, &QDialogButtonBox::rejected, this, &AddHostDialog::reject); + + setModal(true); + + d->mServer = server; + + QWidget *page = new QWidget(this); + mainLayout->addWidget(page); + mainLayout->addWidget(buttonBox); + QHBoxLayout *layout = new QHBoxLayout(page); + layout->setContentsMargins(0, 0, 0, 0); + + d->mCfg = new KLDAP::LdapConfigWidget( + KLDAP::LdapConfigWidget::W_USER + |KLDAP::LdapConfigWidget::W_PASS + |KLDAP::LdapConfigWidget::W_BINDDN + |KLDAP::LdapConfigWidget::W_REALM + |KLDAP::LdapConfigWidget::W_HOST + |KLDAP::LdapConfigWidget::W_PORT + |KLDAP::LdapConfigWidget::W_VER + |KLDAP::LdapConfigWidget::W_TIMELIMIT + |KLDAP::LdapConfigWidget::W_SIZELIMIT + |KLDAP::LdapConfigWidget::W_PAGESIZE + |KLDAP::LdapConfigWidget::W_DN + |KLDAP::LdapConfigWidget::W_FILTER + |KLDAP::LdapConfigWidget::W_SECBOX + |KLDAP::LdapConfigWidget::W_AUTHBOX, + page); + + layout->addWidget(d->mCfg); + d->mCfg->setHost(d->mServer->host()); + d->mCfg->setPort(d->mServer->port()); + d->mCfg->setDn(d->mServer->baseDn()); + d->mCfg->setUser(d->mServer->user()); + d->mCfg->setBindDn(d->mServer->bindDn()); + d->mCfg->setPassword(d->mServer->password()); + d->mCfg->setTimeLimit(d->mServer->timeLimit()); + d->mCfg->setSizeLimit(d->mServer->sizeLimit()); + d->mCfg->setPageSize(d->mServer->pageSize()); + d->mCfg->setVersion(d->mServer->version()); + d->mCfg->setFilter(d->mServer->filter()); + switch (d->mServer->security()) { + case KLDAP::LdapServer::TLS: + d->mCfg->setSecurity(KLDAP::LdapConfigWidget::TLS); + break; + case KLDAP::LdapServer::SSL: + d->mCfg->setSecurity(KLDAP::LdapConfigWidget::SSL); + break; + default: + d->mCfg->setSecurity(KLDAP::LdapConfigWidget::None); + } + + switch (d->mServer->auth()) { + case KLDAP::LdapServer::Simple: + d->mCfg->setAuth(KLDAP::LdapConfigWidget::Simple); + break; + case KLDAP::LdapServer::SASL: + d->mCfg->setAuth(KLDAP::LdapConfigWidget::SASL); + break; + default: + d->mCfg->setAuth(KLDAP::LdapConfigWidget::Anonymous); + } + d->mCfg->setMech(d->mServer->mech()); + + KAcceleratorManager::manage(this); + connect(d->mCfg, &KLDAP::LdapConfigWidget::hostNameChanged, this, &AddHostDialog::slotHostEditChanged); + connect(d->mOkButton, &QPushButton::clicked, this, &AddHostDialog::slotOk); + d->mOkButton->setEnabled(!d->mServer->host().isEmpty()); + d->readConfig(); +} + +AddHostDialog::~AddHostDialog() +{ + delete d; +} + +void AddHostDialog::slotHostEditChanged(const QString &text) +{ + d->mOkButton->setEnabled(!text.isEmpty()); +} + +void AddHostDialog::slotOk() +{ + d->mServer->setHost(d->mCfg->host()); + d->mServer->setPort(d->mCfg->port()); + d->mServer->setBaseDn(d->mCfg->dn()); + d->mServer->setUser(d->mCfg->user()); + d->mServer->setBindDn(d->mCfg->bindDn()); + d->mServer->setPassword(d->mCfg->password()); + d->mServer->setTimeLimit(d->mCfg->timeLimit()); + d->mServer->setSizeLimit(d->mCfg->sizeLimit()); + d->mServer->setPageSize(d->mCfg->pageSize()); + d->mServer->setVersion(d->mCfg->version()); + d->mServer->setFilter(d->mCfg->filter()); + switch (d->mCfg->security()) { + case KLDAP::LdapConfigWidget::TLS: + d->mServer->setSecurity(KLDAP::LdapServer::TLS); + break; + case KLDAP::LdapConfigWidget::SSL: + d->mServer->setSecurity(KLDAP::LdapServer::SSL); + break; + default: + d->mServer->setSecurity(KLDAP::LdapServer::None); + } + switch (d->mCfg->auth()) { + case KLDAP::LdapConfigWidget::Simple: + d->mServer->setAuth(KLDAP::LdapServer::Simple); + break; + case KLDAP::LdapConfigWidget::SASL: + d->mServer->setAuth(KLDAP::LdapServer::SASL); + break; + default: + d->mServer->setAuth(KLDAP::LdapServer::Anonymous); + } + d->mServer->setMech(d->mCfg->mech()); + QDialog::accept(); +} + +#include "moc_addhostdialog.cpp" diff --git a/src/widgets/addhostdialog.h b/src/widgets/addhostdialog.h new file mode 100644 index 0000000..c515868 --- /dev/null +++ b/src/widgets/addhostdialog.h @@ -0,0 +1,55 @@ +/* + This file is part of libkldap. + + Copyright (c) 2002-2010 Tobias Koenig + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef ADDHOSTDIALOG_H +#define ADDHOSTDIALOG_H + +#include "kldap_export.h" +#include + +namespace KLDAP { +class LdapServer; +class AddHostDialogPrivate; +/** + * @brief The AddHostDialog class + * @author Laurent Montel + */ +class KLDAP_EXPORT AddHostDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AddHostDialog(KLDAP::LdapServer *server, QWidget *parent = nullptr); + ~AddHostDialog(); + +Q_SIGNALS: + void changed(bool); + +private Q_SLOTS: + void slotHostEditChanged(const QString &); + void slotOk(); + +private: + AddHostDialogPrivate *const d; +}; +} + +#endif // ADDHOSTDIALOG_H diff --git a/src/widgets/ldapclient.cpp b/src/widgets/ldapclient.cpp new file mode 100644 index 0000000..e516dc2 --- /dev/null +++ b/src/widgets/ldapclient.cpp @@ -0,0 +1,299 @@ +/* kldapclient.cpp - LDAP access + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * + * Author: Steffen Hansen + * + * Ported to KABC by Daniel Molkentin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "ldapclient.h" +#include "ldapclient_debug.h" + +#include +#include +#include +#include + +#include + +#include + +using namespace KLDAP; + +class Q_DECL_HIDDEN LdapClient::Private +{ +public: + Private(LdapClient *qq) + : q(qq) + { + } + + ~Private() + { + q->cancelQuery(); + } + + void startParseLDIF(); + void parseLDIF(const QByteArray &data); + void endParseLDIF(); + void finishCurrentObject(); + + void slotData(KIO::Job *, const QByteArray &data); + void slotInfoMessage(KJob *, const QString &info, const QString &); + void slotDone(); + + LdapClient *q = nullptr; + + KLDAP::LdapServer mServer; + QString mScope; + QStringList mAttrs; + + QPointer mJob = nullptr; + bool mActive = false; + + KLDAP::LdapObject mCurrentObject; + KLDAP::Ldif mLdif; + int mClientNumber = 0; + int mCompletionWeight = 0; +}; + +LdapClient::LdapClient(int clientNumber, QObject *parent) + : QObject(parent) + , d(new Private(this)) +{ + d->mClientNumber = clientNumber; + d->mCompletionWeight = 50 - d->mClientNumber; +} + +LdapClient::~LdapClient() +{ + delete d; +} + +bool LdapClient::isActive() const +{ + return d->mActive; +} + +void LdapClient::setServer(const KLDAP::LdapServer &server) +{ + d->mServer = server; +} + +const KLDAP::LdapServer LdapClient::server() const +{ + return d->mServer; +} + +void LdapClient::setAttributes(const QStringList &attrs) +{ + d->mAttrs = attrs; + d->mAttrs << QStringLiteral("objectClass"); // via objectClass we detect distribution lists +} + +QStringList LdapClient::attributes() const +{ + return d->mAttrs; +} + +void LdapClient::setScope(const QString &scope) +{ + d->mScope = scope; +} + +void LdapClient::startQuery(const QString &filter) +{ + cancelQuery(); + KLDAP::LdapUrl url; + + url = d->mServer.url(); + + url.setAttributes(d->mAttrs); + url.setScope(d->mScope == QLatin1String("one") ? KLDAP::LdapUrl::One : KLDAP::LdapUrl::Sub); + const QString userFilter = url.filter(); + QString finalFilter = filter; + // combine the filter set by the user in the config dialog (url.filter()) and the filter from this query + if (!userFilter.isEmpty()) { + finalFilter = QLatin1String("&(") + finalFilter + QLatin1String(")(") + userFilter + QLatin1Char(')'); + } + url.setFilter(QLatin1Char('(') + finalFilter + QLatin1Char(')')); + + qCDebug(LDAPCLIENT_LOG) << "LdapClient: Doing query:" << url.toDisplayString(); + + d->startParseLDIF(); + d->mActive = true; + KIO::TransferJob *transfertJob = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo); + d->mJob = transfertJob; + connect(transfertJob, &KIO::TransferJob::data, this, [this](KIO::Job *job, const QByteArray &data) { + d->slotData(job, data); + }); + connect(d->mJob.data(), &KIO::TransferJob::infoMessage, this, + [this](KJob *job, const QString &str, const QString &val) { + d->slotInfoMessage(job, str, val); + }); + connect(d->mJob.data(), &KIO::TransferJob::result, + this, [this]() { + d->slotDone(); + }); +} + +void LdapClient::cancelQuery() +{ + if (d->mJob) { + d->mJob->kill(); + d->mJob = nullptr; + } + + d->mActive = false; +} + +void LdapClient::Private::slotData(KIO::Job *, const QByteArray &data) +{ + parseLDIF(data); +} + +void LdapClient::Private::slotInfoMessage(KJob *, const QString &info, const QString &) +{ + qCDebug(LDAPCLIENT_LOG) << "Job said :" << info; +} + +void LdapClient::Private::slotDone() +{ + endParseLDIF(); + mActive = false; + if (!mJob) { + return; + } + int err = mJob->error(); + if (err && err != KIO::ERR_USER_CANCELED) { + Q_EMIT q->error(mJob->errorString()); + } + Q_EMIT q->done(); +} + +void LdapClient::Private::startParseLDIF() +{ + mCurrentObject.clear(); + mLdif.startParsing(); +} + +void LdapClient::Private::endParseLDIF() +{ +} + +void LdapClient::Private::finishCurrentObject() +{ + mCurrentObject.setDn(mLdif.dn()); + KLDAP::LdapAttrValue objectclasses; + const KLDAP::LdapAttrMap::ConstIterator end = mCurrentObject.attributes().constEnd(); + for (KLDAP::LdapAttrMap::ConstIterator it = mCurrentObject.attributes().constBegin(); + it != end; ++it) { + if (it.key().toLower() == QLatin1String("objectclass")) { + objectclasses = it.value(); + break; + } + } + + bool groupofnames = false; + const KLDAP::LdapAttrValue::ConstIterator endValue(objectclasses.constEnd()); + for (KLDAP::LdapAttrValue::ConstIterator it = objectclasses.constBegin(); + it != endValue; ++it) { + const QByteArray sClass = (*it).toLower(); + if (sClass == "groupofnames" || sClass == "kolabgroupofnames") { + groupofnames = true; + } + } + + if (groupofnames) { + KLDAP::LdapAttrMap::ConstIterator it = mCurrentObject.attributes().find(QStringLiteral("mail")); + if (it == mCurrentObject.attributes().end()) { + // No explicit mail address found so far? + // Fine, then we use the address stored in the DN. + QString sMail; +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) + const QStringList lMail = mCurrentObject.dn().toString().split(QStringLiteral(",dc="), QString::SkipEmptyParts); +#else + const QStringList lMail = mCurrentObject.dn().toString().split(QStringLiteral(",dc="), Qt::SkipEmptyParts); +#endif + const int n = lMail.count(); + if (n) { + if (lMail.first().startsWith(QLatin1String("cn="), Qt::CaseInsensitive)) { + sMail = lMail.first().simplified().mid(3); + if (1 < n) { + sMail.append(QLatin1Char('@')); + } + for (int i = 1; i < n; ++i) { + sMail.append(lMail.at(i)); + if (i < n - 1) { + sMail.append(QLatin1Char('.')); + } + } + mCurrentObject.addValue(QStringLiteral("mail"), sMail.toUtf8()); + } + } + } + } + Q_EMIT q->result(*q, mCurrentObject); + mCurrentObject.clear(); +} + +void LdapClient::Private::parseLDIF(const QByteArray &data) +{ + //qCDebug(LDAPCLIENT_LOG) <<"LdapClient::parseLDIF(" << QCString(data.data(), data.size()+1) <<" )"; + if (!data.isEmpty()) { + mLdif.setLdif(data); + } else { + mLdif.endLdif(); + } + KLDAP::Ldif::ParseValue ret; + QString name; + do { + ret = mLdif.nextItem(); + switch (ret) { + case KLDAP::Ldif::Item: + { + name = mLdif.attr(); + const QByteArray value = mLdif.value(); + mCurrentObject.addValue(name, value); + break; + } + case KLDAP::Ldif::EndEntry: + finishCurrentObject(); + break; + default: + break; + } + } while (ret != KLDAP::Ldif::MoreData); +} + +int LdapClient::clientNumber() const +{ + return d->mClientNumber; +} + +int LdapClient::completionWeight() const +{ + return d->mCompletionWeight; +} + +void LdapClient::setCompletionWeight(int weight) +{ + d->mCompletionWeight = weight; +} + +#include "moc_ldapclient.cpp" diff --git a/src/widgets/ldapclient.h b/src/widgets/ldapclient.h new file mode 100644 index 0000000..8d1b907 --- /dev/null +++ b/src/widgets/ldapclient.h @@ -0,0 +1,155 @@ +/* kldapclient.h - LDAP access + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * + * Author: Steffen Hansen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KLDAP_LDAPCLIENT_H +#define KLDAP_LDAPCLIENT_H + +#include "kldap_export.h" + +#include +#include + +namespace KLDAP { +class LdapObject; +class LdapServer; + +/** + * @short An object that represents a configured LDAP server. + * + * This class represents a client that to an LDAP server that + * can be used for LDAP lookups. Every client is identified by + * a unique numeric id. + * + * @since 4.5 + */ +class KLDAP_EXPORT LdapClient : public QObject +{ + Q_OBJECT + +public: + /** + * Creates a new ldap client. + * + * @param clientNumber The unique number of this client. + * @param parent The parent object. + */ + explicit LdapClient(int clientNumber, QObject *parent = nullptr); + + /** + * Destroys the ldap client. + */ + ~LdapClient() override; + + /** + * Returns the number of this client. + */ + int clientNumber() const; + + /** + * Returns whether this client is currently running + * a search query. + */ + bool isActive() const; + + /** + * Sets the completion @p weight of this client. + * + * This value will be used to sort the results of this + * client when used for auto completion. + */ + void setCompletionWeight(int weight); + + /** + * Returns the completion weight of this client. + */ + int completionWeight() const; + + /** + * Sets the LDAP @p server information that shall be + * used by this client. + */ + void setServer(const KLDAP::LdapServer &server); + + /** + * Returns the ldap server information that are used + * by this client. + */ + const KLDAP::LdapServer server() const; + + /** + * Sets the LDAP @p attributes that should be returned + * in the query result. + * + * Pass an empty list to include all available attributes. + */ + void setAttributes(const QStringList &attributes); + + /** + * Returns the LDAP attributes that should be returned + * in the query result. + */ + QStringList attributes() const; + + /** + * Sets the @p scope of the LDAP query. + * + * Valid values are 'one' or 'sub'. + */ + void setScope(const QString &scope); + + /** + * Starts the query with the given @p filter. + */ + void startQuery(const QString &filter); + + /** + * Cancels a running query. + */ + void cancelQuery(); + +Q_SIGNALS: + /** + * This signal is emitted when the query has finished. + */ + void done(); + + /** + * This signal is emitted in case of an error. + * + * @param message A message that describes the error. + */ + void error(const QString &message); + + /** + * This signal is emitted once for each object that is + * returned from the query + */ + void result(const KLDAP::LdapClient &client, const KLDAP::LdapObject &); + +private: + //@cond PRIVATE + class Private; + Private *const d; + //@endcond +}; +} + +#endif diff --git a/src/widgets/ldapclientsearch.cpp b/src/widgets/ldapclientsearch.cpp new file mode 100644 index 0000000..f8a189f --- /dev/null +++ b/src/widgets/ldapclientsearch.cpp @@ -0,0 +1,430 @@ +/* kldapclient.cpp - LDAP access + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * + * Author: Steffen Hansen + * + * Ported to KABC by Daniel Molkentin + * + * Copyright (C) 2013-2020 Laurent Montel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "ldapclientsearch.h" +#include "ldapclientsearchconfig.h" +#include "ldapclient_debug.h" + +#include "ldapclient.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +using namespace KLDAP; + +class Q_DECL_HIDDEN LdapClientSearch::Private +{ +public: + Private(LdapClientSearch *qq) + : q(qq) + , mActiveClients(0) + , mNoLDAPLookup(false) + { + mClientSearchConfig = new LdapClientSearchConfig; + } + + ~Private() + { + delete mClientSearchConfig; + } + + void readWeighForClient(LdapClient *client, const KConfigGroup &config, int clientNumber); + void readConfig(); + void finish(); + void makeSearchData(QStringList &ret, LdapResult::List &resList); + + void slotLDAPResult(const KLDAP::LdapClient &client, const KLDAP::LdapObject &); + void slotLDAPError(const QString &); + void slotLDAPDone(); + void slotDataTimer(); + void slotFileChanged(const QString &); + + LdapClientSearch *q = nullptr; + QList mClients; + QStringList mAttributes; + QString mSearchText; + QString mFilter; + QTimer mDataTimer; + int mActiveClients = 0; + bool mNoLDAPLookup = false; + LdapResultObject::List mResults; + QString mConfigFile; + LdapClientSearchConfig *mClientSearchConfig = nullptr; +}; + +LdapClientSearch::LdapClientSearch(QObject *parent) + : QObject(parent) + , d(new Private(this)) +{ + Kdelibs4ConfigMigrator migrate(QStringLiteral("ldapsettings")); + migrate.setConfigFiles(QStringList() << QStringLiteral("kabldaprc")); + migrate.migrate(); + + if (!KProtocolInfo::isKnownProtocol(QUrl(QStringLiteral("ldap://localhost")))) { + d->mNoLDAPLookup = true; + return; + } + + d->mAttributes << QStringLiteral("cn") + << QStringLiteral("mail") + << QStringLiteral("givenname") + << QStringLiteral("sn"); + + // Set the filter, to make sure old usage (before 4.14) of this object still works. + d->mFilter = QStringLiteral("&(|(objectclass=person)(objectclass=groupOfNames)(mail=*))" + "(|(cn=%1*)(mail=%1*)(givenName=%1*)(sn=%1*))"); + + d->readConfig(); + connect(KDirWatch::self(), &KDirWatch::dirty, this, [this](const QString &filename) { + d->slotFileChanged(filename); + }); +} + +LdapClientSearch::~LdapClientSearch() +{ + delete d; +} + +void LdapClientSearch::Private::readWeighForClient(LdapClient *client, const KConfigGroup &config, int clientNumber) +{ + const int completionWeight = config.readEntry(QStringLiteral("SelectedCompletionWeight%1").arg(clientNumber), -1); + if (completionWeight != -1) { + client->setCompletionWeight(completionWeight); + } +} + +void LdapClientSearch::updateCompletionWeights() +{ + KConfigGroup config(KLDAP::LdapClientSearchConfig::config(), "LDAP"); + for (int i = 0; i < d->mClients.size(); ++i) { + d->readWeighForClient(d->mClients[ i ], config, i); + } +} + +QList LdapClientSearch::clients() const +{ + return d->mClients; +} + +QString LdapClientSearch::filter() const +{ + return d->mFilter; +} + +void LdapClientSearch::setFilter(const QString &filter) +{ + d->mFilter = filter; +} + +QStringList LdapClientSearch::attributes() const +{ + return d->mAttributes; +} + +void LdapClientSearch::setAttributes(const QStringList &attrs) +{ + if (attrs != d->mAttributes) { + d->mAttributes = attrs; + d->readConfig(); + } +} + +void LdapClientSearch::Private::readConfig() +{ + q->cancelSearch(); + qDeleteAll(mClients); + mClients.clear(); + + // stolen from KAddressBook + KConfigGroup config(KLDAP::LdapClientSearchConfig::config(), "LDAP"); + const int numHosts = config.readEntry("NumSelectedHosts", 0); + if (!numHosts) { + mNoLDAPLookup = true; + } else { + for (int j = 0; j < numHosts; ++j) { + LdapClient *ldapClient = new LdapClient(j, q); + KLDAP::LdapServer server; + mClientSearchConfig->readConfig(server, config, j, true); + if (!server.host().isEmpty()) { + mNoLDAPLookup = false; + } + ldapClient->setServer(server); + + readWeighForClient(ldapClient, config, j); + + ldapClient->setAttributes(mAttributes); + + q->connect(ldapClient, &LdapClient::result, + q, [this](const LdapClient &client, const KLDAP::LdapObject &obj) { + slotLDAPResult(client, obj); + }); + q->connect(ldapClient, &LdapClient::done, + q, [this]() { + slotLDAPDone(); + }); + q->connect(ldapClient, qOverload(&LdapClient::error), + q, [this](const QString &str) { + slotLDAPError(str); + }); + + mClients.append(ldapClient); + } + + q->connect(&mDataTimer, &QTimer::timeout, q, [this]() { + slotDataTimer(); + }); + } + mConfigFile = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/kabldaprc"); + KDirWatch::self()->addFile(mConfigFile); +} + +void LdapClientSearch::Private::slotFileChanged(const QString &file) +{ + if (file == mConfigFile) { + readConfig(); + } +} + +void LdapClientSearch::startSearch(const QString &txt) +{ + if (d->mNoLDAPLookup) { + QMetaObject::invokeMethod(this, &LdapClientSearch::searchDone, Qt::QueuedConnection); + return; + } + + cancelSearch(); + + int pos = txt.indexOf(QLatin1Char('\"')); + if (pos >= 0) { + ++pos; + const int pos2 = txt.indexOf(QLatin1Char('\"'), pos); + if (pos2 >= 0) { + d->mSearchText = txt.mid(pos, pos2 - pos); + } else { + d->mSearchText = txt.mid(pos); + } + } else { + d->mSearchText = txt; + } + + const QString filter = d->mFilter.arg(d->mSearchText); + + QList::Iterator it(d->mClients.begin()); + const QList::Iterator end(d->mClients.end()); + for (; it != end; ++it) { + (*it)->startQuery(filter); + qCDebug(LDAPCLIENT_LOG) << "LdapClientSearch::startSearch()" << filter; + ++d->mActiveClients; + } +} + +void LdapClientSearch::cancelSearch() +{ + QList::Iterator it(d->mClients.begin()); + const QList::Iterator end(d->mClients.end()); + for (; it != end; ++it) { + (*it)->cancelQuery(); + } + + d->mActiveClients = 0; + d->mResults.clear(); +} + +void LdapClientSearch::Private::slotLDAPResult(const LdapClient &client, const KLDAP::LdapObject &obj) +{ + LdapResultObject result; + result.client = &client; + result.object = obj; + + mResults.append(result); + if (!mDataTimer.isActive()) { + mDataTimer.setSingleShot(true); + mDataTimer.start(500); + } +} + +void LdapClientSearch::Private::slotLDAPError(const QString &) +{ + slotLDAPDone(); +} + +void LdapClientSearch::Private::slotLDAPDone() +{ + if (--mActiveClients > 0) { + return; + } + + finish(); +} + +void LdapClientSearch::Private::slotDataTimer() +{ + QStringList lst; + LdapResult::List reslist; + + Q_EMIT q->searchData(mResults); + + makeSearchData(lst, reslist); + if (!lst.isEmpty()) { + Q_EMIT q->searchData(lst); + } + if (!reslist.isEmpty()) { + Q_EMIT q->searchData(reslist); + } +} + +void LdapClientSearch::Private::finish() +{ + mDataTimer.stop(); + + slotDataTimer(); // Q_EMIT final bunch of data + Q_EMIT q->searchDone(); +} + +void LdapClientSearch::Private::makeSearchData(QStringList &ret, LdapResult::List &resList) +{ + LdapResultObject::List::ConstIterator it1(mResults.constBegin()); + const LdapResultObject::List::ConstIterator end1(mResults.constEnd()); + for (; it1 != end1; ++it1) { + QString name, mail, givenname, sn; + QStringList mails; + bool isDistributionList = false; + bool wasCN = false; + bool wasDC = false; + + //qCDebug(LDAPCLIENT_LOG) <<"\n\nLdapClientSearch::makeSearchData()"; + + KLDAP::LdapAttrMap::ConstIterator it2; + for (it2 = (*it1).object.attributes().constBegin(); + it2 != (*it1).object.attributes().constEnd(); ++it2) { + QByteArray val = (*it2).first(); + int len = val.size(); + if (len > 0 && '\0' == val[len - 1]) { + --len; + } + const QString tmp = QString::fromUtf8(val.constData(), len); + //qCDebug(LDAPCLIENT_LOG) <<" key: \"" << it2.key() <<"\" value: \"" << tmp <<"\""; + if (it2.key() == QLatin1String("cn")) { + name = tmp; + if (mail.isEmpty()) { + mail = tmp; + } else { + if (wasCN) { + mail.prepend(QLatin1Char('.')); + } else { + mail.prepend(QLatin1Char('@')); + } + mail.prepend(tmp); + } + wasCN = true; + } else if (it2.key() == QLatin1String("dc")) { + if (mail.isEmpty()) { + mail = tmp; + } else { + if (wasDC) { + mail.append(QLatin1Char('.')); + } else { + mail.append(QLatin1Char('@')); + } + mail.append(tmp); + } + wasDC = true; + } else if (it2.key() == QLatin1String("mail")) { + mail = tmp; + KLDAP::LdapAttrValue::ConstIterator it3 = it2.value().constBegin(); + for (; it3 != it2.value().constEnd(); ++it3) { + mails.append(QString::fromUtf8((*it3).data(), (*it3).size())); + } + } else if (it2.key() == QLatin1String("givenName")) { + givenname = tmp; + } else if (it2.key() == QLatin1String("sn")) { + sn = tmp; + } else if (it2.key() == QLatin1String("objectClass") + && (tmp == QLatin1String("groupOfNames") || tmp == QLatin1String("kolabGroupOfNames"))) { + isDistributionList = true; + } + } + + if (mails.isEmpty()) { + if (!mail.isEmpty()) { + mails.append(mail); + } + if (isDistributionList) { + //qCDebug(LDAPCLIENT_LOG) <<"\n\nLdapClientSearch::makeSearchData() found a list:" << name; + ret.append(name); + // following lines commented out for bugfixing kolab issue #177: + // + // Unlike we thought previously we may NOT append the server name here. + // + // The right server is found by the SMTP server instead: Kolab users + // must use the correct SMTP server, by definition. + // + //mail = (*it1).client->base().simplified(); + //mail.replace( ",dc=", ".", false ); + //if( mail.startsWith("dc=", false) ) + // mail.remove(0, 3); + //mail.prepend( '@' ); + //mail.prepend( name ); + //mail = name; + } else { + continue; // nothing, bad entry + } + } else if (name.isEmpty()) { + ret.append(mail); + } else { + ret.append(QStringLiteral("%1 <%2>").arg(name, mail)); + } + + LdapResult sr; + sr.dn = (*it1).object.dn(); + sr.clientNumber = (*it1).client->clientNumber(); + sr.completionWeight = (*it1).client->completionWeight(); + sr.name = name; + sr.email = mails; + resList.append(sr); + } + + mResults.clear(); +} + +bool LdapClientSearch::isAvailable() const +{ + return !d->mNoLDAPLookup; +} + +#include "moc_ldapclientsearch.cpp" diff --git a/src/widgets/ldapclientsearch.h b/src/widgets/ldapclientsearch.h new file mode 100644 index 0000000..42d6c6c --- /dev/null +++ b/src/widgets/ldapclientsearch.h @@ -0,0 +1,181 @@ +/* kldapclient.h - LDAP access + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * + * Author: Steffen Hansen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef LDAPCLIENTSEARCH_H +#define LDAPCLIENTSEARCH_H + +#include "kldap_export.h" + +#include +#include +#include + +namespace KLDAP { +class LdapClient; + +/** + * Describes the result returned by an LdapClientSearch query. + * + * @since 4.14 + */ +struct LdapResultObject { + typedef QVector List; + const LdapClient *client = nullptr; + KLDAP::LdapObject object; +}; + +/** + * Describes the result returned by an LdapClientSearch query. + * + * @since 4.5 + */ +struct LdapResult { + /** + * A list of LdapResult objects. + */ + typedef QVector List; + + LdapDN dn; + QString name; ///< The full name of the contact. + QStringList email; ///< The list of emails of the contact. + int clientNumber; ///< The client the contact comes from (used for sorting in a ldap-only lookup). + int completionWeight; ///< The weight of the contact (used for sorting in a completion list). +}; + +/** + * @since 4.5 + */ +class KLDAP_EXPORT LdapClientSearch : public QObject +{ + Q_OBJECT + +public: + /** + * Creates a new ldap client search object. + * + * @param parent The parent object. + */ + explicit LdapClientSearch(QObject *parent = nullptr); + + /** + * Destroys the ldap client search object. + */ + ~LdapClientSearch(); + + /** + * Starts the LDAP search on all configured LDAP clients with the given search @p query. + */ + void startSearch(const QString &query); + + /** + * Cancels the currently running search query. + */ + void cancelSearch(); + + /** + * Returns whether LDAP search is possible at all. + * + * @note This method can return @c false if either no LDAP is configured + * or the system does not support the KIO LDAP protocol. + */ + bool isAvailable() const; + + /** + * Updates the completion weights for the configured LDAP clients from + * the configuration file. + */ + void updateCompletionWeights(); + + /** + * Returns the list of configured LDAP clients. + */ + QList clients() const; + + /** + * Returns the filter for the Query + * + * @since 4.14 + */ + QString filter() const; + + /** + * Sets the filter for the Query + * + * @since 4.14 + */ + void setFilter(const QString &); + + /** + * Returns the attributes, that are queried the LDAP Server. + * + * @since 4.14 + */ + QStringList attributes() const; + + /** + * Sets the attributes, that are queried the LDAP Server. + * + * @since 4.14 + */ + void setAttributes(const QStringList &); + +Q_SIGNALS: + /** + * This signal is emitted whenever new contacts have been found + * during the lookup. + * + * @param results The contacts in the form "Full Name " + */ + void searchData(const QStringList &results); + + /** + * This signal is emitted whenever new contacts have been found + * during the lookup. + * + * @param results The list of found contacts. + */ + void searchData(const KLDAP::LdapResult::List &results); + + /** + * This signal is emitted whenever new contacts have been found + * during the lookup. + * + * @param results The list of found contacts. + */ + void searchData(const KLDAP::LdapResultObject::List &results); + + /** + * This signal is emitted whenever the lookup is complete or the + * user has canceled the query. + */ + void searchDone(); + +private: + //@cond PRIVATE + class Private; + Private *const d; + //@endcond +}; +} +Q_DECLARE_TYPEINFO(KLDAP::LdapResult, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(KLDAP::LdapResultObject, Q_MOVABLE_TYPE); + +#endif // LDAPCLIENTSEARCH_H diff --git a/src/widgets/ldapclientsearchconfig.cpp b/src/widgets/ldapclientsearchconfig.cpp new file mode 100644 index 0000000..2d7dce0 --- /dev/null +++ b/src/widgets/ldapclientsearchconfig.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2013-2020 Laurent Montel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "ldapclientsearchconfig.h" +#include + +#include +#include +#include +#include +#include + +using namespace KLDAP; + +class Q_DECL_HIDDEN LdapClientSearchConfig::Private +{ +public: + Private() + { + } + + ~Private() + { + if (useWallet) { + wallet->deleteLater(); + wallet = nullptr; + } + } + + KWallet::Wallet *wallet = nullptr; + bool useWallet = false; + bool askWallet = true; +}; + +Q_GLOBAL_STATIC_WITH_ARGS(KConfig, s_config, (QLatin1String("kabldaprc"), KConfig::NoGlobals)) + +KConfig *LdapClientSearchConfig::config() +{ + return s_config; +} + +LdapClientSearchConfig::LdapClientSearchConfig(QObject *parent) + : QObject(parent) + , d(new LdapClientSearchConfig::Private()) +{ +} + +LdapClientSearchConfig::~LdapClientSearchConfig() +{ + delete d; +} + +void LdapClientSearchConfig::clearWalletPassword() +{ + if (!d->wallet) { + d->wallet = KWallet::Wallet::openWallet(KWallet::Wallet::LocalWallet(), 0); + } + if (d->wallet) { + d->useWallet = true; + if (d->wallet->hasFolder(QStringLiteral("ldapclient"))) { + //Recreate it. + d->wallet->removeFolder(QStringLiteral("ldapclient")); + d->wallet->createFolder(QStringLiteral("ldapclient")); + d->wallet->setFolder(QStringLiteral("ldapclient")); + } + } +} + +void LdapClientSearchConfig::readConfig(KLDAP::LdapServer &server, KConfigGroup &config, int j, bool active) +{ + QString prefix; + if (active) { + prefix = QStringLiteral("Selected"); + } + + const QString host = config.readEntry(prefix + QStringLiteral("Host%1").arg(j), + QString()).trimmed(); + if (!host.isEmpty()) { + server.setHost(host); + } + + const int port = config.readEntry(prefix + QStringLiteral("Port%1").arg(j), 389); + server.setPort(port); + + const QString base = config.readEntry(prefix + QStringLiteral("Base%1").arg(j), + QString()).trimmed(); + if (!base.isEmpty()) { + server.setBaseDn(KLDAP::LdapDN(base)); + } + + const QString user = config.readEntry(prefix + QStringLiteral("User%1").arg(j), + QString()).trimmed(); + if (!user.isEmpty()) { + server.setUser(user); + } + + const QString bindDN = config.readEntry(prefix + QStringLiteral("Bind%1").arg(j), QString()).trimmed(); + if (!bindDN.isEmpty()) { + server.setBindDn(bindDN); + } + + const QString pwdBindBNEntry = prefix + QStringLiteral("PwdBind%1").arg(j); + QString pwdBindDN = config.readEntry(pwdBindBNEntry, QString()); + if (!pwdBindDN.isEmpty()) { + if (d->askWallet && KMessageBox::Yes == KMessageBox::questionYesNo(nullptr, i18n("LDAP password is stored as clear text, do you want to store it in kwallet?"), + i18n("Store clear text password in KWallet"), + KStandardGuiItem::yes(), + KStandardGuiItem::no(), + QStringLiteral("DoAskToStoreToKwallet"))) { + d->wallet = KWallet::Wallet::openWallet(KWallet::Wallet::LocalWallet(), 0); + if (d->wallet) { + connect(d->wallet, &KWallet::Wallet::walletClosed, this, &LdapClientSearchConfig::slotWalletClosed); + d->useWallet = true; + if (!d->wallet->hasFolder(QStringLiteral("ldapclient"))) { + d->wallet->createFolder(QStringLiteral("ldapclient")); + } + d->wallet->setFolder(QStringLiteral("ldapclient")); + d->wallet->writePassword(pwdBindBNEntry, pwdBindDN); + config.deleteEntry(pwdBindBNEntry); + config.sync(); + } + } + server.setPassword(pwdBindDN); + } else if (d->askWallet) { //Look at in Wallet + d->wallet = KWallet::Wallet::openWallet(KWallet::Wallet::LocalWallet(), 0); + if (d->wallet) { + d->useWallet = true; + if (!d->wallet->setFolder(QStringLiteral("ldapclient"))) { + d->wallet->createFolder(QStringLiteral("ldapclient")); + d->wallet->setFolder(QStringLiteral("ldapclient")); + } + d->wallet->readPassword(pwdBindBNEntry, pwdBindDN); + if (!pwdBindDN.isEmpty()) { + server.setPassword(pwdBindDN); + } + } else { + d->useWallet = false; + } + } + + server.setTimeLimit(config.readEntry(prefix + QStringLiteral("TimeLimit%1").arg(j), 0)); + server.setSizeLimit(config.readEntry(prefix + QStringLiteral("SizeLimit%1").arg(j), 0)); + server.setPageSize(config.readEntry(prefix + QStringLiteral("PageSize%1").arg(j), 0)); + server.setVersion(config.readEntry(prefix + QStringLiteral("Version%1").arg(j), 3)); + + QString tmp; + tmp = config.readEntry(prefix + QStringLiteral("Security%1").arg(j), + QStringLiteral("None")); + server.setSecurity(KLDAP::LdapServer::None); + if (tmp == QLatin1String("SSL")) { + server.setSecurity(KLDAP::LdapServer::SSL); + } else if (tmp == QLatin1String("TLS")) { + server.setSecurity(KLDAP::LdapServer::TLS); + } + + tmp = config.readEntry(prefix + QStringLiteral("Auth%1").arg(j), + QStringLiteral("Anonymous")); + server.setAuth(KLDAP::LdapServer::Anonymous); + if (tmp == QLatin1String("Simple")) { + server.setAuth(KLDAP::LdapServer::Simple); + } else if (tmp == QLatin1String("SASL")) { + server.setAuth(KLDAP::LdapServer::SASL); + } + + server.setMech(config.readEntry(prefix + QStringLiteral("Mech%1").arg(j), QString())); + server.setFilter(config.readEntry(prefix + QStringLiteral("UserFilter%1").arg(j), QString())); + server.setCompletionWeight(config.readEntry(prefix + QStringLiteral("CompletionWeight%1").arg(j), -1)); +} + +void LdapClientSearchConfig::writeConfig(const KLDAP::LdapServer &server, KConfigGroup &config, int j, bool active) +{ + QString prefix; + if (active) { + prefix = QStringLiteral("Selected"); + } + + config.writeEntry(prefix + QStringLiteral("Host%1").arg(j), server.host()); + config.writeEntry(prefix + QStringLiteral("Port%1").arg(j), server.port()); + config.writeEntry(prefix + QStringLiteral("Base%1").arg(j), server.baseDn().toString()); + config.writeEntry(prefix + QStringLiteral("User%1").arg(j), server.user()); + config.writeEntry(prefix + QStringLiteral("Bind%1").arg(j), server.bindDn()); + + const QString passwordEntry = prefix + QStringLiteral("PwdBind%1").arg(j); + const QString password = server.password(); + if (!password.isEmpty()) { + if (d->useWallet && !d->wallet) { + d->wallet = KWallet::Wallet::openWallet(KWallet::Wallet::LocalWallet(), 0); + } + if (d->wallet) { + d->wallet->writePassword(passwordEntry, password); + } else { + config.writeEntry(passwordEntry, password); + d->useWallet = false; + } + } + + config.writeEntry(prefix + QStringLiteral("TimeLimit%1").arg(j), server.timeLimit()); + config.writeEntry(prefix + QStringLiteral("SizeLimit%1").arg(j), server.sizeLimit()); + config.writeEntry(prefix + QStringLiteral("PageSize%1").arg(j), server.pageSize()); + config.writeEntry(prefix + QStringLiteral("Version%1").arg(j), server.version()); + QString tmp; + switch (server.security()) { + case KLDAP::LdapServer::TLS: + tmp = QStringLiteral("TLS"); + break; + case KLDAP::LdapServer::SSL: + tmp = QStringLiteral("SSL"); + break; + default: + tmp = QStringLiteral("None"); + } + config.writeEntry(prefix + QStringLiteral("Security%1").arg(j), tmp); + switch (server.auth()) { + case KLDAP::LdapServer::Simple: + tmp = QStringLiteral("Simple"); + break; + case KLDAP::LdapServer::SASL: + tmp = QStringLiteral("SASL"); + break; + default: + tmp = QStringLiteral("Anonymous"); + } + config.writeEntry(prefix + QStringLiteral("Auth%1").arg(j), tmp); + config.writeEntry(prefix + QStringLiteral("Mech%1").arg(j), server.mech()); + config.writeEntry(prefix + QStringLiteral("UserFilter%1").arg(j), server.filter().trimmed()); + if (server.completionWeight() > -1) { + config.writeEntry(prefix + QStringLiteral("CompletionWeight%1").arg(j), server.completionWeight()); + } +} + +void LdapClientSearchConfig::slotWalletClosed() +{ + delete d->wallet; + d->wallet = nullptr; +} + +void LdapClientSearchConfig::askForWallet(bool askForWallet) +{ + d->askWallet = askForWallet; +} diff --git a/src/widgets/ldapclientsearchconfig.h b/src/widgets/ldapclientsearchconfig.h new file mode 100644 index 0000000..8198fbe --- /dev/null +++ b/src/widgets/ldapclientsearchconfig.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2013-2020 Laurent Montel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef LDAPCLIENTSEARCHCONFIG_H +#define LDAPCLIENTSEARCHCONFIG_H + +#include "kldap_export.h" + +#include + +class KConfigGroup; +class KConfig; + +namespace KLDAP { +class LdapServer; +class LdapClient; +/** + * @brief The LdapClientSearchConfig class + * @author Laurent Montel + */ +class KLDAP_EXPORT LdapClientSearchConfig : public QObject +{ + Q_OBJECT +public: + explicit LdapClientSearchConfig(QObject *parent = nullptr); + ~LdapClientSearchConfig(); + + /** + * Returns the global config object, which stores the LdapClient configurations. + */ + static KConfig *config(); + + /** + * Reads the LDAP @p server settings from the given config @p group for the + * given LDAP @p clientNumber. + * + * @param active Defines whether the active settings shall be read. + */ + void readConfig(KLDAP::LdapServer &server, KConfigGroup &group, int clientNumber, bool active); + + /** + * Writes the LDAP @p server settings to the given config @p group for the + * given LDAP @p clientNumber. + * + * @param active Defines whether the active settings shall be written. + */ + void writeConfig(const KLDAP::LdapServer &server, KConfigGroup &group, int clientNumber, bool active); + + /** + * Should LdapClientSearchConfig ask, if it should use the KWallet to store passwords + */ + void askForWallet(bool askForWallet); + + void clearWalletPassword(); +private Q_SLOTS: + void slotWalletClosed(); + +private: + //@cond PRIVATE + class Private; + Private *const d; +}; +} + +#endif // LDAPCLIENTSEARCHCONFIG_H diff --git a/src/widgets/ldapconfigurewidget.cpp b/src/widgets/ldapconfigurewidget.cpp new file mode 100644 index 0000000..e5c0b19 --- /dev/null +++ b/src/widgets/ldapconfigurewidget.cpp @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2019-2020 Laurent Montel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "ldapconfigurewidget.h" +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "ldapclientsearch.h" +#include "ldapclientsearchconfig.h" +#include + +#include "addhostdialog.h" + +using namespace KLDAP; +class LDAPItem : public QListWidgetItem +{ +public: + LDAPItem(QListWidget *parent, const KLDAP::LdapServer &server, bool isActive = false) + : QListWidgetItem(parent, QListWidgetItem::UserType) + , mIsActive(isActive) + { + setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable); + setCheckState(isActive ? Qt::Checked : Qt::Unchecked); + setServer(server); + } + + void setServer(const KLDAP::LdapServer &server) + { + mServer = server; + + setText(mServer.host()); + } + + const KLDAP::LdapServer &server() const + { + return mServer; + } + + void setIsActive(bool isActive) + { + mIsActive = isActive; + } + + bool isActive() const + { + return mIsActive; + } + +private: + KLDAP::LdapServer mServer; + bool mIsActive = false; +}; + +LdapConfigureWidget::LdapConfigureWidget(QWidget *parent) + : QWidget(parent) +{ + mClientSearchConfig = new KLDAP::LdapClientSearchConfig; + initGUI(); + + connect(mHostListView, &QListWidget::currentItemChanged, this, &LdapConfigureWidget::slotSelectionChanged); + connect(mHostListView, &QListWidget::itemDoubleClicked, this, &LdapConfigureWidget::slotEditHost); + connect(mHostListView, &QListWidget::itemClicked, this, &LdapConfigureWidget::slotItemClicked); + + connect(mUpButton, &QToolButton::clicked, this, &LdapConfigureWidget::slotMoveUp); + connect(mDownButton, &QToolButton::clicked, this, &LdapConfigureWidget::slotMoveDown); +} + +LdapConfigureWidget::~LdapConfigureWidget() +{ + delete mClientSearchConfig; +} + +void LdapConfigureWidget::slotSelectionChanged(QListWidgetItem *item) +{ + bool state = (item != nullptr); + mEditButton->setEnabled(state); + mRemoveButton->setEnabled(state); + mDownButton->setEnabled(item && (mHostListView->row(item) != (mHostListView->count() - 1))); + mUpButton->setEnabled(item && (mHostListView->row(item) != 0)); +} + +void LdapConfigureWidget::slotItemClicked(QListWidgetItem *item) +{ + LDAPItem *ldapItem = dynamic_cast(item); + if (!ldapItem) { + return; + } + + if ((ldapItem->checkState() == Qt::Checked) != ldapItem->isActive()) { + Q_EMIT changed(true); + ldapItem->setIsActive(ldapItem->checkState() == Qt::Checked); + } +} + +void LdapConfigureWidget::slotAddHost() +{ + KLDAP::LdapServer server; + KLDAP::AddHostDialog dlg(&server, this); + + if (dlg.exec() && !server.host().trimmed().isEmpty()) { //krazy:exclude=crashy + new LDAPItem(mHostListView, server); + + Q_EMIT changed(true); + } +} + +void LdapConfigureWidget::slotEditHost() +{ + LDAPItem *item = dynamic_cast(mHostListView->currentItem()); + if (!item) { + return; + } + + KLDAP::LdapServer server = item->server(); + KLDAP::AddHostDialog dlg(&server, this); + dlg.setWindowTitle(i18nc("@title:window", "Edit Host")); + + if (dlg.exec() && !server.host().isEmpty()) { //krazy:exclude=crashy + item->setServer(server); + + Q_EMIT changed(true); + } +} + +void LdapConfigureWidget::slotRemoveHost() +{ + QListWidgetItem *item = mHostListView->currentItem(); + if (!item) { + return; + } + LDAPItem *ldapItem = dynamic_cast(item); + if (KMessageBox::No == KMessageBox::questionYesNo(this, i18n("Do you want to remove setting for host \"%1\"?", ldapItem->server().host()), i18n("Remove Host"))) { + return; + } + + delete mHostListView->takeItem(mHostListView->currentRow()); + + slotSelectionChanged(mHostListView->currentItem()); + + Q_EMIT changed(true); +} + +static void swapItems(LDAPItem *item, LDAPItem *other) +{ + KLDAP::LdapServer server = item->server(); + bool isActive = item->isActive(); + item->setServer(other->server()); + item->setIsActive(other->isActive()); + item->setCheckState(other->isActive() ? Qt::Checked : Qt::Unchecked); + other->setServer(server); + other->setIsActive(isActive); + other->setCheckState(isActive ? Qt::Checked : Qt::Unchecked); +} + +void LdapConfigureWidget::slotMoveUp() +{ + const QList selectedItems = mHostListView->selectedItems(); + if (selectedItems.isEmpty()) { + return; + } + + LDAPItem *item = static_cast(mHostListView->selectedItems().first()); + if (!item) { + return; + } + + LDAPItem *above = static_cast(mHostListView->item(mHostListView->row(item) - 1)); + if (!above) { + return; + } + + swapItems(item, above); + + mHostListView->setCurrentItem(above); + above->setSelected(true); + + Q_EMIT changed(true); +} + +void LdapConfigureWidget::slotMoveDown() +{ + const QList selectedItems = mHostListView->selectedItems(); + if (selectedItems.isEmpty()) { + return; + } + + LDAPItem *item = static_cast(mHostListView->selectedItems().first()); + if (!item) { + return; + } + + LDAPItem *below = static_cast(mHostListView->item(mHostListView->row(item) + 1)); + if (!below) { + return; + } + + swapItems(item, below); + + mHostListView->setCurrentItem(below); + below->setSelected(true); + + Q_EMIT changed(true); +} + +void LdapConfigureWidget::load() +{ + mHostListView->clear(); + KConfig *config = KLDAP::LdapClientSearchConfig::config(); + KConfigGroup group(config, "LDAP"); + + int count = group.readEntry("NumSelectedHosts", 0); + for (int i = 0; i < count; ++i) { + KLDAP::LdapServer server; + mClientSearchConfig->readConfig(server, group, i, true); + LDAPItem *item = new LDAPItem(mHostListView, server, true); + item->setCheckState(Qt::Checked); + } + + count = group.readEntry("NumHosts", 0); + for (int i = 0; i < count; ++i) { + KLDAP::LdapServer server; + mClientSearchConfig->readConfig(server, group, i, false); + new LDAPItem(mHostListView, server); + } + + Q_EMIT changed(false); +} + +void LdapConfigureWidget::save() +{ + mClientSearchConfig->clearWalletPassword(); + KConfig *config = KLDAP::LdapClientSearchConfig::config(); + config->deleteGroup("LDAP"); + + KConfigGroup group(config, "LDAP"); + + int selected = 0; + int unselected = 0; + for (int i = 0; i < mHostListView->count(); ++i) { + LDAPItem *item = dynamic_cast(mHostListView->item(i)); + if (!item) { + continue; + } + + KLDAP::LdapServer server = item->server(); + if (item->checkState() == Qt::Checked) { + mClientSearchConfig->writeConfig(server, group, selected, true); + selected++; + } else { + mClientSearchConfig->writeConfig(server, group, unselected, false); + unselected++; + } + } + + group.writeEntry("NumSelectedHosts", selected); + group.writeEntry("NumHosts", unselected); + config->sync(); + + Q_EMIT changed(false); +} + +void LdapConfigureWidget::initGUI() +{ + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setObjectName(QStringLiteral("layout")); + layout->setContentsMargins(0, 0, 0, 0); + + QGroupBox *groupBox = new QGroupBox(i18n("LDAP Servers")); + layout->addWidget(groupBox); + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->setObjectName(QStringLiteral("mainlayout")); + groupBox->setLayout(mainLayout); + + // Contents of the QVGroupBox: label and hbox + QLabel *label = new QLabel(i18n("Check all servers that should be used:")); + mainLayout->addWidget(label); + + QWidget *hBox = new QWidget(this); + mainLayout->addWidget(hBox); + + QHBoxLayout *hBoxHBoxLayout = new QHBoxLayout(hBox); + hBoxHBoxLayout->setContentsMargins(0, 0, 0, 0); + hBoxHBoxLayout->setSpacing(6); + // Contents of the hbox: listview and up/down buttons on the right (vbox) + mHostListView = new QListWidget(hBox); + hBoxHBoxLayout->addWidget(mHostListView); + mHostListView->setSortingEnabled(false); + + QWidget *upDownBox = new QWidget(hBox); + QVBoxLayout *upDownBoxVBoxLayout = new QVBoxLayout(upDownBox); + upDownBoxVBoxLayout->setContentsMargins(0, 0, 0, 0); + hBoxHBoxLayout->addWidget(upDownBox); + upDownBoxVBoxLayout->setSpacing(6); + mUpButton = new QToolButton(upDownBox); + upDownBoxVBoxLayout->addWidget(mUpButton); + mUpButton->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); + mUpButton->setEnabled(false); // b/c no item is selected yet + + mDownButton = new QToolButton(upDownBox); + upDownBoxVBoxLayout->addWidget(mDownButton); + mDownButton->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); + mDownButton->setEnabled(false); // b/c no item is selected yet + + QWidget *spacer = new QWidget(upDownBox); + upDownBoxVBoxLayout->addWidget(spacer); + upDownBoxVBoxLayout->setStretchFactor(spacer, 100); + + QDialogButtonBox *buttons = new QDialogButtonBox(this); + QPushButton *add = buttons->addButton(i18n("&Add Host..."), + QDialogButtonBox::ActionRole); + connect(add, &QPushButton::clicked, this, &LdapConfigureWidget::slotAddHost); + mEditButton = buttons->addButton(i18n("&Edit Host..."), + QDialogButtonBox::ActionRole); + connect(mEditButton, &QPushButton::clicked, this, &LdapConfigureWidget::slotEditHost); + mEditButton->setEnabled(false); + mRemoveButton = buttons->addButton(i18n("&Remove Host"), + QDialogButtonBox::ActionRole); + connect(mRemoveButton, &QPushButton::clicked, this, &LdapConfigureWidget::slotRemoveHost); + mRemoveButton->setEnabled(false); + buttons->layout(); + + layout->addWidget(buttons); +} diff --git a/src/widgets/ldapconfigurewidget.h b/src/widgets/ldapconfigurewidget.h new file mode 100644 index 0000000..d9ae119 --- /dev/null +++ b/src/widgets/ldapconfigurewidget.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2019-2020 Laurent Montel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef LDAPCONFIGUREWIDGET_H +#define LDAPCONFIGUREWIDGET_H + + +#include "kldap_export.h" + +#include + +class QListWidget; +class QPushButton; +class QToolButton; +class QListWidgetItem; + +namespace KLDAP { +class LdapClientSearchConfig; +/** + * @brief The LdapConfigureWidget class + * @author Laurent Montel + */ +class KLDAP_EXPORT LdapConfigureWidget : public QWidget +{ + Q_OBJECT +public: + explicit LdapConfigureWidget(QWidget *parent = nullptr); + ~LdapConfigureWidget(); + + void load(); + void save(); + +private Q_SLOTS: + void slotAddHost(); + void slotEditHost(); + void slotRemoveHost(); + void slotSelectionChanged(QListWidgetItem *); + void slotItemClicked(QListWidgetItem *); + void slotMoveUp(); + void slotMoveDown(); + +Q_SIGNALS: + void changed(bool); + +private: + void initGUI(); + + QListWidget *mHostListView = nullptr; + + QPushButton *mAddButton = nullptr; + QPushButton *mEditButton = nullptr; + QPushButton *mRemoveButton = nullptr; + + QToolButton *mUpButton = nullptr; + QToolButton *mDownButton = nullptr; + KLDAP::LdapClientSearchConfig *mClientSearchConfig = nullptr; +}; +} + +#endif // LDAPCONFIGUREWIDGET_H diff --git a/src/ldapconfigwidget.cpp b/src/widgets/ldapconfigwidget.cpp similarity index 100% rename from src/ldapconfigwidget.cpp rename to src/widgets/ldapconfigwidget.cpp diff --git a/src/ldapconfigwidget.h b/src/widgets/ldapconfigwidget.h similarity index 100% rename from src/ldapconfigwidget.h rename to src/widgets/ldapconfigwidget.h diff --git a/src/widgets/ldapsearchdialog.cpp b/src/widgets/ldapsearchdialog.cpp new file mode 100644 index 0000000..7cef57e --- /dev/null +++ b/src/widgets/ldapsearchdialog.cpp @@ -0,0 +1,899 @@ +/* + * This file is part of libkldap. + * + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * + * Author: Steffen Hansen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "ldapsearchdialog.h" + +#include "ldapclient.h" + +#include "ldapclientsearchconfig.h" +#include "widgets/progressindicatorlabel.h" +#include "misc/lineeditcatchreturnkey.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace KLDAP; + +static QString asUtf8(const QByteArray &val) +{ + if (val.isEmpty()) { + return QString(); + } + + const char *data = val.data(); + + //QString::fromUtf8() bug workaround + if (data[ val.size() - 1 ] == '\0') { + return QString::fromUtf8(data, val.size() - 1); + } else { + return QString::fromUtf8(data, val.size()); + } +} + +static QString join(const KLDAP::LdapAttrValue &lst, const QString &sep) +{ + QString res; + bool alredy = false; + KLDAP::LdapAttrValue::ConstIterator end(lst.constEnd()); + for (KLDAP::LdapAttrValue::ConstIterator it = lst.constBegin(); it != end; ++it) { + if (alredy) { + res += sep; + } + + alredy = true; + res += asUtf8(*it); + } + + return res; +} + +static QMap &adrbookattr2ldap() +{ + static QMap keys; + + if (keys.isEmpty()) { + keys[ i18nc("@item LDAP search key", "Title") ] = QStringLiteral("title"); + keys[ i18n("Full Name") ] = QStringLiteral("cn"); + keys[ i18nc("@item LDAP search key", "Email") ] = QStringLiteral("mail"); + keys[ i18n("Home Number") ] = QStringLiteral("homePhone"); + keys[ i18n("Work Number") ] = QStringLiteral("telephoneNumber"); + keys[ i18n("Mobile Number") ] = QStringLiteral("mobile"); + keys[ i18n("Fax Number") ] = QStringLiteral("facsimileTelephoneNumber"); + keys[ i18n("Pager") ] = QStringLiteral("pager"); + keys[ i18n("Street") ] = QStringLiteral("street"); + keys[ i18nc("@item LDAP search key", "State") ] = QStringLiteral("st"); + keys[ i18n("Country") ] = QStringLiteral("co"); + keys[ i18n("City") ] = QStringLiteral("l"); //krazy:exclude=doublequote_chars + keys[ i18n("Organization") ] = QStringLiteral("o"); //krazy:exclude=doublequote_chars + keys[ i18n("Company") ] = QStringLiteral("Company"); + keys[ i18n("Department") ] = QStringLiteral("department"); + keys[ i18n("Zip Code") ] = QStringLiteral("postalCode"); + keys[ i18n("Postal Address") ] = QStringLiteral("postalAddress"); + keys[ i18n("Description") ] = QStringLiteral("description"); + keys[ i18n("User ID") ] = QStringLiteral("uid"); + } + + return keys; +} + +static QString makeFilter(const QString &query, LdapSearchDialog::FilterType attr, bool startsWith) +{ + /* The reasoning behind this filter is: + * If it's a person, or a distlist, show it, even if it doesn't have an email address. + * If it's not a person, or a distlist, only show it if it has an email attribute. + * This allows both resource accounts with an email address which are not a person and + * person entries without an email address to show up, while still not showing things + * like structural entries in the ldap tree. */ + QString result(QStringLiteral("&(|(objectclass=person)(objectclass=groupofnames)(mail=*))(")); + if (query.isEmpty()) { + // Return a filter that matches everything + return result + QStringLiteral("|(cn=*)(sn=*)") + QLatin1Char(')'); + } + + if (attr == LdapSearchDialog::Name) { + result += startsWith ? QStringLiteral("|(cn=%1*)(sn=%2*)") : QStringLiteral("|(cn=*%1*)(sn=*%2*)"); + result = result.arg(query, query); + } else { + result += startsWith ? QStringLiteral("%1=%2*") : QStringLiteral("%1=*%2*"); + if (attr == LdapSearchDialog::Email) { + result = result.arg(QStringLiteral("mail"), query); + } else if (attr == LdapSearchDialog::HomeNumber) { + result = result.arg(QStringLiteral("homePhone"), query); + } else if (attr == LdapSearchDialog::WorkNumber) { + result = result.arg(QStringLiteral("telephoneNumber"), query); + } else { + // Error? + result.clear(); + return result; + } + } + result += QLatin1Char(')'); + return result; +} + +static KContacts::Addressee convertLdapAttributesToAddressee(const KLDAP::LdapAttrMap &attrs) +{ + KContacts::Addressee addr; + + // name + if (!attrs.value(QStringLiteral("cn")).isEmpty()) { + addr.setNameFromString(asUtf8(attrs[QStringLiteral("cn")].first())); + } + + // email + KLDAP::LdapAttrValue lst = attrs[QStringLiteral("mail")]; + KLDAP::LdapAttrValue::ConstIterator it = lst.constBegin(); + bool pref = true; + while (it != lst.constEnd()) { + addr.insertEmail(asUtf8(*it), pref); + pref = false; + ++it; + } + + if (!attrs.value(QStringLiteral("o")).isEmpty()) { + addr.setOrganization(asUtf8(attrs[ QStringLiteral("o") ].first())); + } + if (addr.organization().isEmpty() && !attrs.value(QStringLiteral("Company")).isEmpty()) { + addr.setOrganization(asUtf8(attrs[ QStringLiteral("Company") ].first())); + } + + // Address + KContacts::Address workAddr(KContacts::Address::Work); + + if (!attrs.value(QStringLiteral("department")).isEmpty()) { + addr.setDepartment(asUtf8(attrs[ QStringLiteral("department") ].first())); + } + + if (!workAddr.isEmpty()) { + addr.insertAddress(workAddr); + } + + // phone + if (!attrs.value(QStringLiteral("homePhone")).isEmpty()) { + KContacts::PhoneNumber homeNr = asUtf8(attrs[ QStringLiteral("homePhone") ].first()); + homeNr.setType(KContacts::PhoneNumber::Home); + addr.insertPhoneNumber(homeNr); + } + + if (!attrs.value(QStringLiteral("telephoneNumber")).isEmpty()) { + KContacts::PhoneNumber workNr = asUtf8(attrs[ QStringLiteral("telephoneNumber") ].first()); + workNr.setType(KContacts::PhoneNumber::Work); + addr.insertPhoneNumber(workNr); + } + + if (!attrs.value(QStringLiteral("facsimileTelephoneNumber")).isEmpty()) { + KContacts::PhoneNumber faxNr = asUtf8(attrs[ QStringLiteral("facsimileTelephoneNumber") ].first()); + faxNr.setType(KContacts::PhoneNumber::Fax); + addr.insertPhoneNumber(faxNr); + } + + if (!attrs.value(QStringLiteral("mobile")).isEmpty()) { + KContacts::PhoneNumber cellNr = asUtf8(attrs[ QStringLiteral("mobile") ].first()); + cellNr.setType(KContacts::PhoneNumber::Cell); + addr.insertPhoneNumber(cellNr); + } + + if (!attrs.value(QStringLiteral("pager")).isEmpty()) { + KContacts::PhoneNumber pagerNr = asUtf8(attrs[ QStringLiteral("pager") ].first()); + pagerNr.setType(KContacts::PhoneNumber::Pager); + addr.insertPhoneNumber(pagerNr); + } + + return addr; +} + +class ContactListModel : public QAbstractTableModel +{ +public: + enum Role { + ServerRole = Qt::UserRole + 1 + }; + + explicit ContactListModel(QObject *parent) + : QAbstractTableModel(parent) + { + } + + void addContact(const KLDAP::LdapAttrMap &contact, const QString &server) + { + beginResetModel(); + mContactList.append(contact); + mServerList.append(server); + endResetModel(); + } + + QPair contact(const QModelIndex &index) const + { + if (!index.isValid() || index.row() < 0 || index.row() >= mContactList.count()) { + return qMakePair(KLDAP::LdapAttrMap(), QString()); + } + + return qMakePair(mContactList.at(index.row()), mServerList.at(index.row())); + } + + QString email(const QModelIndex &index) const + { + if (!index.isValid() || index.row() < 0 || index.row() >= mContactList.count()) { + return QString(); + } + + return asUtf8(mContactList.at(index.row()).value(QStringLiteral("mail")).first()).trimmed(); + } + + QString fullName(const QModelIndex &index) const + { + if (!index.isValid() || index.row() < 0 || index.row() >= mContactList.count()) { + return QString(); + } + + return asUtf8(mContactList.at(index.row()).value(QStringLiteral("cn")).first()).trimmed(); + } + + void clear() + { + beginResetModel(); + mContactList.clear(); + mServerList.clear(); + endResetModel(); + } + + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + if (!parent.isValid()) { + return mContactList.count(); + } else { + return 0; + } + } + + int columnCount(const QModelIndex &parent = QModelIndex()) const override + { + if (!parent.isValid()) { + return 18; + } else { + return 0; + } + } + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override + { + if (orientation == Qt::Vertical || role != Qt::DisplayRole || section < 0 || section > 17) { + return QVariant(); + } + + switch (section) { + case 0: + return i18n("Full Name"); + case 1: + return i18nc("@title:column Column containing email addresses", "Email"); + case 2: + return i18n("Home Number"); + case 3: + return i18n("Work Number"); + case 4: + return i18n("Mobile Number"); + case 5: + return i18n("Fax Number"); + case 6: + return i18n("Company"); + case 7: + return i18n("Organization"); + case 8: + return i18n("Street"); + case 9: + return i18nc("@title:column Column containing the residential state of the address", + "State"); + case 10: + return i18n("Country"); + case 11: + return i18n("Zip Code"); + case 12: + return i18n("Postal Address"); + case 13: + return i18n("City"); + case 14: + return i18n("Department"); + case 15: + return i18n("Description"); + case 16: + return i18n("User ID"); + case 17: + return i18nc("@title:column Column containing title of the person", "Title"); + default: + return QVariant(); + } + } + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override + { + if (!index.isValid()) { + return QVariant(); + } + + if (index.row() < 0 || index.row() >= mContactList.count() + || index.column() < 0 || index.column() > 17) { + return QVariant(); + } + + if (role == ServerRole) { + return mServerList.at(index.row()); + } + + if ((role != Qt::DisplayRole) && (role != Qt::ToolTipRole)) { + return QVariant(); + } + + const KLDAP::LdapAttrMap map = mContactList.at(index.row()); + + switch (index.column()) { + case 0: + return join(map.value(QStringLiteral("cn")), QStringLiteral(", ")); + case 1: + return join(map.value(QStringLiteral("mail")), QStringLiteral(", ")); + case 2: + return join(map.value(QStringLiteral("homePhone")), QStringLiteral(", ")); + case 3: + return join(map.value(QStringLiteral("telephoneNumber")), QStringLiteral(", ")); + case 4: + return join(map.value(QStringLiteral("mobile")), QStringLiteral(", ")); + case 5: + return join(map.value(QStringLiteral("facsimileTelephoneNumber")), QStringLiteral(", ")); + case 6: + return join(map.value(QStringLiteral("Company")), QStringLiteral(", ")); + case 7: + return join(map.value(QStringLiteral("o")), QStringLiteral(", ")); + case 8: + return join(map.value(QStringLiteral("street")), QStringLiteral(", ")); + case 9: + return join(map.value(QStringLiteral("st")), QStringLiteral(", ")); + case 10: + return join(map.value(QStringLiteral("co")), QStringLiteral(", ")); + case 11: + return join(map.value(QStringLiteral("postalCode")), QStringLiteral(", ")); + case 12: + return join(map.value(QStringLiteral("postalAddress")), QStringLiteral(", ")); + case 13: + return join(map.value(QStringLiteral("l")), QStringLiteral(", ")); + case 14: + return join(map.value(QStringLiteral("department")), QStringLiteral(", ")); + case 15: + return join(map.value(QStringLiteral("description")), QStringLiteral(", ")); + case 16: + return join(map.value(QStringLiteral("uid")), QStringLiteral(", ")); + case 17: + return join(map.value(QStringLiteral("title")), QStringLiteral(", ")); + default: + return QVariant(); + } + } + +private: + QVector mContactList; + QStringList mServerList; +}; + +class Q_DECL_HIDDEN LdapSearchDialog::Private +{ +public: + Private(LdapSearchDialog *qq) + : q(qq) + { + } + + QList< QPair > selectedItems() + { + QList< QPair > contacts; + + const QModelIndexList selected = mResultView->selectionModel()->selectedRows(); + const int numberOfSelectedElement(selected.count()); + contacts.reserve(numberOfSelectedElement); + for (int i = 0; i < numberOfSelectedElement; ++i) { + contacts.append(mModel->contact(sortproxy->mapToSource(selected.at(i)))); + } + + return contacts; + } + + void saveSettings(); + void restoreSettings(); + void cancelQuery(); + + void slotAddResult(const KLDAP::LdapClient &, const KLDAP::LdapObject &); + void slotSetScope(bool); + void slotStartSearch(); + void slotStopSearch(); + void slotSearchDone(); + void slotError(const QString &); + void slotSelectAll(); + void slotUnselectAll(); + void slotSelectionChanged(); + + LdapSearchDialog *q; + KGuiItem startSearchGuiItem; + KGuiItem stopSearchGuiItem; + int mNumHosts = 0; + QList mLdapClientList; + bool mIsConfigured = false; + KContacts::Addressee::List mSelectedContacts; + + QComboBox *mFilterCombo = nullptr; + QComboBox *mSearchType = nullptr; + QLineEdit *mSearchEdit = nullptr; + + QCheckBox *mRecursiveCheckbox = nullptr; + QTableView *mResultView = nullptr; + QPushButton *mSearchButton = nullptr; + ContactListModel *mModel = nullptr; + KPIM::ProgressIndicatorLabel *progressIndication = nullptr; + QSortFilterProxyModel *sortproxy = nullptr; + QLineEdit *searchLine = nullptr; + QPushButton *user1Button = nullptr; +}; + +LdapSearchDialog::LdapSearchDialog(QWidget *parent) + : QDialog(parent) + , d(new Private(this)) +{ + setWindowTitle(i18nc("@title:window", "Import Contacts from LDAP")); + QVBoxLayout *mainLayout = new QVBoxLayout(this); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel, this); + d->user1Button = new QPushButton; + buttonBox->addButton(d->user1Button, QDialogButtonBox::ActionRole); + + QPushButton *user2Button = new QPushButton; + buttonBox->addButton(user2Button, QDialogButtonBox::ActionRole); + + connect(d->user1Button, &QPushButton::clicked, this, &LdapSearchDialog::slotUser1); + connect(user2Button, &QPushButton::clicked, this, &LdapSearchDialog::slotUser2); + connect(buttonBox, &QDialogButtonBox::rejected, this, &LdapSearchDialog::slotCancelClicked); + d->user1Button->setDefault(true); + setModal(false); + KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::close()); + QFrame *page = new QFrame(this); + mainLayout->addWidget(page); + mainLayout->addWidget(buttonBox); + + QVBoxLayout *topLayout = new QVBoxLayout(page); + topLayout->setContentsMargins(0, 0, 0, 0); + + QGroupBox *groupBox = new QGroupBox(i18n("Search for Addresses in Directory"), + page); + QGridLayout *boxLayout = new QGridLayout(); + groupBox->setLayout(boxLayout); + boxLayout->setColumnStretch(1, 1); + + QLabel *label = new QLabel(i18n("Search for:"), groupBox); + boxLayout->addWidget(label, 0, 0); + + d->mSearchEdit = new QLineEdit(groupBox); + d->mSearchEdit->setClearButtonEnabled(true); + boxLayout->addWidget(d->mSearchEdit, 0, 1); + label->setBuddy(d->mSearchEdit); + + label = new QLabel(i18nc("In LDAP attribute", "in"), groupBox); + boxLayout->addWidget(label, 0, 2); + + d->mFilterCombo = new QComboBox(groupBox); + d->mFilterCombo->addItem(i18nc("@item:inlistbox Name of the contact", "Name"), QVariant::fromValue(Name)); + d->mFilterCombo->addItem(i18nc("@item:inlistbox email address of the contact", "Email"), QVariant::fromValue(Email)); + d->mFilterCombo->addItem(i18nc("@item:inlistbox", "Home Number"), QVariant::fromValue(HomeNumber)); + d->mFilterCombo->addItem(i18nc("@item:inlistbox", "Work Number"), QVariant::fromValue(WorkNumber)); + boxLayout->addWidget(d->mFilterCombo, 0, 3); + d->startSearchGuiItem = KGuiItem(i18nc("@action:button Start searching", "&Search"), QStringLiteral("edit-find")); + d->stopSearchGuiItem = KStandardGuiItem::stop(); + + QSize buttonSize; + d->mSearchButton = new QPushButton(groupBox); + KGuiItem::assign(d->mSearchButton, d->startSearchGuiItem); + + buttonSize = d->mSearchButton->sizeHint(); + if (buttonSize.width() < d->mSearchButton->sizeHint().width()) { + buttonSize = d->mSearchButton->sizeHint(); + } + d->mSearchButton->setFixedWidth(buttonSize.width()); + + d->mSearchButton->setDefault(true); + boxLayout->addWidget(d->mSearchButton, 0, 4); + + d->mRecursiveCheckbox = new QCheckBox(i18n("Recursive search"), groupBox); + d->mRecursiveCheckbox->setChecked(true); + boxLayout->addWidget(d->mRecursiveCheckbox, 1, 0, 1, 5); + + d->mSearchType = new QComboBox(groupBox); + d->mSearchType->addItem(i18n("Contains")); + d->mSearchType->addItem(i18n("Starts With")); + boxLayout->addWidget(d->mSearchType, 1, 3, 1, 2); + + topLayout->addWidget(groupBox); + + QHBoxLayout *quickSearchLineLayout = new QHBoxLayout; + quickSearchLineLayout->addStretch(); + d->searchLine = new QLineEdit; + new KPIM::LineEditCatchReturnKey(d->searchLine, this); + d->searchLine->setClearButtonEnabled(true); + d->searchLine->setPlaceholderText(i18n("Search in result")); + quickSearchLineLayout->addWidget(d->searchLine); + topLayout->addLayout(quickSearchLineLayout); + + d->mResultView = new QTableView(page); + d->mResultView->setSelectionMode(QTableView::MultiSelection); + d->mResultView->setSelectionBehavior(QTableView::SelectRows); + d->mModel = new ContactListModel(d->mResultView); + + d->sortproxy = new QSortFilterProxyModel(this); + d->sortproxy->setFilterKeyColumn(-1); //Search in all column + d->sortproxy->setSourceModel(d->mModel); + d->sortproxy->setFilterCaseSensitivity(Qt::CaseInsensitive); + connect(d->searchLine, &QLineEdit::textChanged, d->sortproxy, &QSortFilterProxyModel::setFilterFixedString); + + d->mResultView->setModel(d->sortproxy); + d->mResultView->verticalHeader()->hide(); + d->mResultView->setSortingEnabled(true); + d->mResultView->horizontalHeader()->setSortIndicatorShown(true); + connect(d->mResultView, qOverload(&QTableView::clicked), this, [this]() { + d->slotSelectionChanged(); + }); + topLayout->addWidget(d->mResultView); + + d->mResultView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(d->mResultView, &QTableView::customContextMenuRequested, this, &LdapSearchDialog::slotCustomContextMenuRequested); + + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->setContentsMargins(0, 0, 0, 0); + topLayout->addLayout(buttonLayout); + + d->progressIndication = new KPIM::ProgressIndicatorLabel(i18n("Searching...")); + buttonLayout->addWidget(d->progressIndication); + + QDialogButtonBox *buttons = new QDialogButtonBox(page); + QPushButton *button = buttons->addButton(i18n("Select All"), + QDialogButtonBox::ActionRole); + connect(button, &QPushButton::clicked, this, [this]() { + d->slotSelectAll(); + }); + button = buttons->addButton(i18n("Unselect All"), + QDialogButtonBox::ActionRole); + connect(button, &QPushButton::clicked, this, [this]() { + d->slotUnselectAll(); + }); + + buttonLayout->addWidget(buttons); + + d->user1Button->setText(i18n("Add Selected")); + user2Button->setText(i18n("Configure LDAP Servers...")); + + connect(d->mRecursiveCheckbox, &QCheckBox::toggled, + this, [this](bool state) { + d->slotSetScope(state); + }); + connect(d->mSearchButton, SIGNAL(clicked()), + this, SLOT(slotStartSearch())); + + setTabOrder(d->mSearchEdit, d->mFilterCombo); + setTabOrder(d->mFilterCombo, d->mSearchButton); + d->mSearchEdit->setFocus(); + + d->slotSelectionChanged(); + d->restoreSettings(); +} + +LdapSearchDialog::~LdapSearchDialog() +{ + d->saveSettings(); + delete d; +} + +void LdapSearchDialog::setSearchText(const QString &text) +{ + d->mSearchEdit->setText(text); +} + +KContacts::Addressee::List LdapSearchDialog::selectedContacts() const +{ + return d->mSelectedContacts; +} + +void LdapSearchDialog::slotCustomContextMenuRequested(const QPoint &pos) +{ + const QModelIndex index = d->mResultView->indexAt(pos); + if (index.isValid()) { + QMenu menu(this); + QAction *act = menu.addAction(i18n("Copy")); + if (menu.exec(QCursor::pos()) == act) { + QClipboard *cb = QApplication::clipboard(); + cb->setText(index.data().toString(), QClipboard::Clipboard); + } + } +} + +void LdapSearchDialog::Private::slotSelectionChanged() +{ + user1Button->setEnabled(mResultView->selectionModel()->hasSelection()); +} + +void LdapSearchDialog::Private::restoreSettings() +{ + // Create one KLDAP::LdapClient per selected server and configure it. + + // First clean the list to make sure it is empty at + // the beginning of the process + qDeleteAll(mLdapClientList); + mLdapClientList.clear(); + + KConfig *config = KLDAP::LdapClientSearchConfig::config(); + + KConfigGroup searchGroup(config, "LDAPSearch"); + mSearchType->setCurrentIndex(searchGroup.readEntry("SearchType", 0)); + + // then read the config file and register all selected + // server in the list + KConfigGroup group(config, "LDAP"); + mNumHosts = group.readEntry("NumSelectedHosts", 0); + if (!mNumHosts) { + mIsConfigured = false; + } else { + mIsConfigured = true; + KLDAP::LdapClientSearchConfig *clientSearchConfig = new KLDAP::LdapClientSearchConfig; + for (int j = 0; j < mNumHosts; ++j) { + KLDAP::LdapServer ldapServer; + KLDAP::LdapClient *ldapClient = new KLDAP::LdapClient(0, q); + clientSearchConfig->readConfig(ldapServer, group, j, true); + ldapClient->setServer(ldapServer); + QStringList attrs; + + QMap::ConstIterator end(adrbookattr2ldap().constEnd()); + for (QMap::ConstIterator it = adrbookattr2ldap().constBegin(); + it != end; ++it) { + attrs << *it; + } + + ldapClient->setAttributes(attrs); + + q->connect(ldapClient, SIGNAL(result(KLDAP::LdapClient,KLDAP::LdapObject)), q, SLOT(slotAddResult(KLDAP::LdapClient,KLDAP::LdapObject))); + q->connect(ldapClient, SIGNAL(done()), q, SLOT(slotSearchDone())); + q->connect(ldapClient, &LdapClient::error, q, [this](const QString &err) { + slotError(err); + }); + + mLdapClientList.append(ldapClient); + } + delete clientSearchConfig; + + mModel->clear(); + } + KConfigGroup groupHeader(config, "Headers"); + mResultView->horizontalHeader()->restoreState(groupHeader.readEntry("HeaderState", QByteArray())); + + KConfigGroup groupSize(config, "Size"); + const QSize dialogSize = groupSize.readEntry("Size", QSize()); + if (dialogSize.isValid()) { + q->resize(dialogSize); + } else { + q->resize(QSize(600, 400).expandedTo(q->minimumSizeHint())); + } +} + +void LdapSearchDialog::Private::saveSettings() +{ + KConfig *config = KLDAP::LdapClientSearchConfig::config(); + KConfigGroup group(config, "LDAPSearch"); + group.writeEntry("SearchType", mSearchType->currentIndex()); + + KConfigGroup groupHeader(config, "Headers"); + groupHeader.writeEntry("HeaderState", mResultView->horizontalHeader()->saveState()); + groupHeader.sync(); + + KConfigGroup size(config, "Size"); + size.writeEntry("Size", q->size()); + size.sync(); + + group.sync(); +} + +void LdapSearchDialog::Private::cancelQuery() +{ + for (KLDAP::LdapClient *client : qAsConst(mLdapClientList)) { + client->cancelQuery(); + } +} + +void LdapSearchDialog::Private::slotAddResult(const KLDAP::LdapClient &client, const KLDAP::LdapObject &obj) +{ + mModel->addContact(obj.attributes(), client.server().host()); +} + +void LdapSearchDialog::Private::slotSetScope(bool rec) +{ + for (KLDAP::LdapClient *client : qAsConst(mLdapClientList)) { + if (rec) { + client->setScope(QStringLiteral("sub")); + } else { + client->setScope(QStringLiteral("one")); + } + } +} + +void LdapSearchDialog::Private::slotStartSearch() +{ + cancelQuery(); + + if (!mIsConfigured) { + KMessageBox::error(q, i18n("You must select an LDAP server before searching.")); + q->slotUser2(); + return; + } + +#ifndef QT_NO_CURSOR + QApplication::setOverrideCursor(Qt::WaitCursor); +#endif + KGuiItem::assign(mSearchButton, stopSearchGuiItem); + progressIndication->start(); + + q->disconnect(mSearchButton, SIGNAL(clicked()), q, SLOT(slotStartSearch())); + q->connect(mSearchButton, SIGNAL(clicked()), q, SLOT(slotStopSearch())); + + const bool startsWith = (mSearchType->currentIndex() == 1); + + const QString filter = makeFilter(mSearchEdit->text().trimmed(), + mFilterCombo->currentData().value(), startsWith); + + // loop in the list and run the KLDAP::LdapClients + mModel->clear(); + for (KLDAP::LdapClient *client : qAsConst(mLdapClientList)) { + client->startQuery(filter); + } + + saveSettings(); +} + +void LdapSearchDialog::Private::slotStopSearch() +{ + cancelQuery(); + slotSearchDone(); +} + +void LdapSearchDialog::Private::slotSearchDone() +{ + // If there are no more active clients, we are done. + for (KLDAP::LdapClient *client : qAsConst(mLdapClientList)) { + if (client->isActive()) { + return; + } + } + + q->disconnect(mSearchButton, SIGNAL(clicked()), q, SLOT(slotStopSearch())); + q->connect(mSearchButton, SIGNAL(clicked()), q, SLOT(slotStartSearch())); + + KGuiItem::assign(mSearchButton, startSearchGuiItem); + progressIndication->stop(); +#ifndef QT_NO_CURSOR + QApplication::restoreOverrideCursor(); +#endif +} + +void LdapSearchDialog::Private::slotError(const QString &error) +{ +#ifndef QT_NO_CURSOR + QApplication::restoreOverrideCursor(); +#endif + KMessageBox::error(q, error); +} + +void LdapSearchDialog::closeEvent(QCloseEvent *e) +{ + d->slotStopSearch(); + e->accept(); +} + +void LdapSearchDialog::Private::slotUnselectAll() +{ + mResultView->clearSelection(); + slotSelectionChanged(); +} + +void LdapSearchDialog::Private::slotSelectAll() +{ + mResultView->selectAll(); + slotSelectionChanged(); +} + +void LdapSearchDialog::slotUser1() +{ + // Import selected items + + d->mSelectedContacts.clear(); + + const QList< QPair > &items = d->selectedItems(); + + if (!items.isEmpty()) { + const QDateTime now = QDateTime::currentDateTime(); + + for (int i = 0; i < items.count(); ++i) { + KContacts::Addressee contact = convertLdapAttributesToAddressee(items.at(i).first); + + // set a comment where the contact came from + contact.setNote(i18nc("arguments are host name, datetime", + "Imported from LDAP directory %1 on %2", + items.at(i).second, QLocale().toString(now, QLocale::ShortFormat))); + + d->mSelectedContacts.append(contact); + } + } + + d->slotStopSearch(); + Q_EMIT contactsAdded(); + + accept(); +} + +void LdapSearchDialog::slotUser2() +{ + // Configure LDAP servers + + QPointer dialog = new KCMultiDialog(this); + dialog->setWindowTitle(i18nc("@title:window", "Configure the Address Book LDAP Settings")); + dialog->addModule(QStringLiteral("kcmldap.desktop")); + + if (dialog->exec()) { //krazy:exclude=crashy + d->restoreSettings(); + } + delete dialog; +} + +void LdapSearchDialog::slotCancelClicked() +{ + d->slotStopSearch(); + reject(); +} + +#include "moc_ldapsearchdialog.cpp" diff --git a/src/widgets/ldapsearchdialog.h b/src/widgets/ldapsearchdialog.h new file mode 100644 index 0000000..5f286b0 --- /dev/null +++ b/src/widgets/ldapsearchdialog.h @@ -0,0 +1,111 @@ +/* + * This file is part of libkldap. + * + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * + * Author: Steffen Hansen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +//AK_REVIEW: move back to libkdepim, not ready yet +#ifndef KLDAP_LDAPSEARCHDIALOG_H +#define KLDAP_LDAPSEARCHDIALOG_H + +#include "kldap_export.h" + +#include +#include + +class QCloseEvent; + +namespace KLDAP { +class LdapClient; +class LdapObject; + +/** + * @short A dialog to search contacts in a LDAP directory. + * + * This dialog allows the user to search for contacts inside + * a LDAP directory. + * + * @author Steffen Hansen + * @since 4.5 + */ +class KDEPIM_EXPORT LdapSearchDialog : public QDialog +{ + Q_OBJECT + +public: + enum FilterType { + Name = 0, + Email, + HomeNumber, + WorkNumber + }; + + /** + * Creates a new ldap search dialog. + * + * @param parent The parent widget. + */ + explicit LdapSearchDialog(QWidget *parent = nullptr); + + /** + * Destroys the ldap search dialog. + */ + ~LdapSearchDialog() override; + + /** + * Sets the @p text in the search line edit. + */ + void setSearchText(const QString &text); + + /** + * Returns a list of contacts that have been selected + * in the LDAP search. + */ + KContacts::Addressee::List selectedContacts() const; + +Q_SIGNALS: + /** + * This signal is emitted whenever the user clicked the + * 'Add Selected' button. + */ + void contactsAdded(); + +protected Q_SLOTS: + void slotUser1(); + void slotUser2(); + void slotCustomContextMenuRequested(const QPoint &); + void slotCancelClicked(); + +protected: + void closeEvent(QCloseEvent *) override; + +private: + //@cond PRIVATE + class Private; + Private *const d; + + Q_PRIVATE_SLOT(d, void slotAddResult(const KLDAP::LdapClient &, const KLDAP::LdapObject &)) + Q_PRIVATE_SLOT(d, void slotStartSearch()) + Q_PRIVATE_SLOT(d, void slotStopSearch()) + Q_PRIVATE_SLOT(d, void slotSearchDone()) + //@endcond +}; +} +Q_DECLARE_METATYPE(KLDAP::LdapSearchDialog::FilterType) +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..8e3e845 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,13 @@ +########## next target ############### +set(testldapclient_SRCS testldapclient.cpp) + +add_executable(testldapclient ${testldapclient_SRCS}) + +target_link_libraries(testldapclient KF5::I18n KF5::Completion KF5::Ldap KF5::CoreAddons) + + +########### next target ############### +#set(testldapsearchdialog_SRCS ldapsearchdialog_gui.cpp) + +#add_executable(ldapsearchdialog_gui ${testldapsearchdialog_SRCS}) +#target_link_libraries(ldapsearchdialog_gui KF5::Libkdepim KF5::I18n KF5::CoreAddons Qt5::Widgets KF5::Contacts) diff --git a/tests/ldapsearchdialog_gui.cpp b/tests/ldapsearchdialog_gui.cpp new file mode 100644 index 0000000..06f5085 --- /dev/null +++ b/tests/ldapsearchdialog_gui.cpp @@ -0,0 +1,40 @@ +/* + Copyright (c) 2015-2020 Laurent Montel + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. + +*/ + +#include +#include +#include + +#include "../src/libkdepim/ldap/ldapsearchdialog.h" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + QCommandLineParser parser; + //We can't use it otherwise we need to call kbuilsyscoca in test mode too. + //QStandardPaths::setTestModeEnabled(true); + parser.addVersionOption(); + parser.addHelpOption(); + parser.process(app); + + KLDAP::LdapSearchDialog dlg; + dlg.exec(); + return app.exec(); +} diff --git a/tests/testldapclient.cpp b/tests/testldapclient.cpp new file mode 100644 index 0000000..4348a50 --- /dev/null +++ b/tests/testldapclient.cpp @@ -0,0 +1,189 @@ +/* This file is part of the KDE project + Copyright (C) 2005 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "testldapclient.h" + +#include + +#include + +#include + +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + QStandardPaths::setTestModeEnabled(true); + QCommandLineParser parser; + parser.addVersionOption(); + parser.addHelpOption(); + parser.process(app); + + TestLDAPClient test; + test.setup(); + test.runAll(); + test.cleanup(); + qDebug() << "All tests OK."; + return 0; +} + +TestLDAPClient::TestLDAPClient() + : mClient(nullptr) +{ +} + +void TestLDAPClient::setup() +{ +} + +void TestLDAPClient::runAll() +{ + testIntevation(); +} + +bool TestLDAPClient::check(const QString &txt, QString a, QString b) +{ + if (a.isEmpty()) { + a.clear(); + } + + if (b.isEmpty()) { + b.clear(); + } + + if (a == b) { + qDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'..." << "ok"; + } else { + qDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'..." << "KO !"; + cleanup(); + exit(1); + } + + return true; +} + +void TestLDAPClient::cleanup() +{ + mClient = nullptr; +} + +void TestLDAPClient::testIntevation() +{ + qDebug(); + mClient = new KLDAP::LdapClient(0, this); + +#if 0 + mClient->setHost("ca.intevation.de"); + mClient->setPort("389"); + mClient->setBase("o=Intevation GmbH,c=de"); +#endif + + // Same list as in kaddressbook's ldapsearchdialog + QStringList attrs; + attrs << QStringLiteral("l") << QStringLiteral("Company") << QStringLiteral("co") << QStringLiteral("department") << QStringLiteral("description") << QStringLiteral("mail") + << QStringLiteral("facsimileTelephoneNumber") << QStringLiteral("cn") << QStringLiteral("homePhone") << QStringLiteral("mobile") << QStringLiteral("o") + << QStringLiteral("pager") << QStringLiteral("postalAddress") << QStringLiteral("st") << QStringLiteral("street") + << QStringLiteral("title") << QStringLiteral("uid") << QStringLiteral("telephoneNumber") << QStringLiteral("postalCode") << QStringLiteral("objectClass"); + // the list from ldapclient.cpp + //attrs << "cn" << "mail" << "givenname" << "sn" << "objectClass"; + mClient->setAttributes(attrs); + + // Taken from LdapSearch + /* + QString mSearchText = QString::fromUtf8( "Till" ); + QString filter = QString( "&(|(objectclass=person)(objectclass=groupOfNames)(mail=*))" + "(|(cn=%1*)(mail=%2*)(givenName=%3*)(sn=%4*))" ) + .arg( mSearchText ).arg( mSearchText ).arg( mSearchText ).arg( mSearchText ); + */ + + // For some reason a fromUtf8 broke the search for me (no results). + // But this certainly looks fishy, it might break on non-utf8 systems. + QString filter = QStringLiteral("&(|(objectclass=person)(objectclass=groupofnames)(mail=*))" + "(|(cn=*Ägypten MDK*)(sn=*Ägypten MDK*))"); + + connect(mClient, &KLDAP::LdapClient::result, this, &TestLDAPClient::slotLDAPResult); + connect(mClient, &KLDAP::LdapClient::done, this, &TestLDAPClient::slotLDAPDone); + connect(mClient, &KLDAP::LdapClient::error, this, &TestLDAPClient::slotLDAPError); + mClient->startQuery(filter); + + QEventLoop eventLoop; + connect(this, &TestLDAPClient::leaveModality, &eventLoop, &QEventLoop::quit); + eventLoop.exec(QEventLoop::ExcludeUserInputEvents); + + delete mClient; + mClient = nullptr; +} + +// from kaddressbook... ugly though... +static QString asUtf8(const QByteArray &val) +{ + if (val.isEmpty()) { + return QString(); + } + + const char *data = val.data(); + + //QString::fromUtf8() bug workaround + if (data[ val.size() - 1 ] == '\0') { + return QString::fromUtf8(data, val.size() - 1); + } else { + return QString::fromUtf8(data, val.size()); + } +} + +static QString join(const KLDAP::LdapAttrValue &lst, const QString &sep) +{ + QString res; + bool already = false; + for (KLDAP::LdapAttrValue::ConstIterator it = lst.begin(); it != lst.end(); ++it) { + if (already) { + res += sep; + } + + already = true; + res += asUtf8(*it); + } + + return res; +} + +void TestLDAPClient::slotLDAPResult(const KLDAP::LdapClient &, const KLDAP::LdapObject &obj) +{ + QString cn = join(obj.attributes()[ QStringLiteral("cn") ], QStringLiteral(", ")); + qDebug() << " cn:" << cn; + Q_ASSERT(!obj.attributes()[ QStringLiteral("mail") ].isEmpty()); + QString mail = join(obj.attributes()[ QStringLiteral("mail") ], QStringLiteral(", ")); + qDebug() << " mail:" << mail; + Q_ASSERT(mail.contains(QLatin1Char('@'))); +} + +void TestLDAPClient::slotLDAPError(const QString &err) +{ + qDebug() << err; + ::exit(1); +} + +void TestLDAPClient::slotLDAPDone() +{ + qDebug(); + Q_EMIT leaveModality(); +} diff --git a/tests/testldapclient.h b/tests/testldapclient.h new file mode 100644 index 0000000..cb8bd63 --- /dev/null +++ b/tests/testldapclient.h @@ -0,0 +1,58 @@ +/* This file is part of the KDE project + Copyright (C) 2005 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef TESTLDAPCLIENT_H +#define TESTLDAPCLIENT_H + +#include + +#include "widgets/ldapclient.h" + +namespace KLDAP { +class LdapClient; +class LdapObject; +} + +class TestLDAPClient : public QObject +{ + Q_OBJECT + +public: + TestLDAPClient(); + void setup(); + void runAll(); + void cleanup(); + + // tests + void testIntevation(); + +Q_SIGNALS: + void leaveModality(); + +private Q_SLOTS: + void slotLDAPResult(const KLDAP::LdapClient &, const KLDAP::LdapObject &); + void slotLDAPError(const QString &); + void slotLDAPDone(); + +private: + bool check(const QString &, QString, QString); + + KLDAP::LdapClient *mClient = nullptr; +}; + +#endif