File indexing completed on 2025-01-05 04:55:48

0001 /*
0002     ui/editdirectoryservicedialog.cpp
0003 
0004     This file is part of libkleopatra, the KDE keymanagement library
0005     SPDX-FileCopyrightText: 2021 g10 Code GmbH
0006     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include <config-libkleo.h>
0012 
0013 #include "editdirectoryservicedialog.h"
0014 
0015 #include <libkleo/algorithm.h>
0016 #include <libkleo/gnupg.h>
0017 #include <libkleo/keyserverconfig.h>
0018 
0019 #include <KCollapsibleGroupBox>
0020 #include <KConfigGroup>
0021 #include <KGuiItem>
0022 #include <KLocalizedString>
0023 #include <KPasswordLineEdit>
0024 #include <KSharedConfig>
0025 #include <KStandardGuiItem>
0026 
0027 #include <QButtonGroup>
0028 #include <QCheckBox>
0029 #include <QDialogButtonBox>
0030 #include <QGridLayout>
0031 #include <QGroupBox>
0032 #include <QLabel>
0033 #include <QLineEdit>
0034 #include <QPushButton>
0035 #include <QRadioButton>
0036 #include <QSpinBox>
0037 #include <QVBoxLayout>
0038 
0039 using namespace Kleo;
0040 
0041 namespace
0042 {
0043 int defaultPort(KeyserverConnection connection)
0044 {
0045     return connection == KeyserverConnection::TunnelThroughTLS ? 636 : 389;
0046 }
0047 }
0048 
0049 class EditDirectoryServiceDialog::Private
0050 {
0051     EditDirectoryServiceDialog *const q;
0052 
0053     struct Ui {
0054         QLineEdit *hostEdit = nullptr;
0055         QSpinBox *portSpinBox = nullptr;
0056         QCheckBox *useDefaultPortCheckBox = nullptr;
0057         QButtonGroup *authenticationGroup = nullptr;
0058         QLineEdit *userEdit = nullptr;
0059         KPasswordLineEdit *passwordEdit = nullptr;
0060         QButtonGroup *connectionGroup = nullptr;
0061         KCollapsibleGroupBox *advancedSettings = nullptr;
0062         QLineEdit *baseDnEdit = nullptr;
0063         QLineEdit *additionalFlagsEdit = nullptr;
0064         QDialogButtonBox *buttonBox = nullptr;
0065 
0066         Ui(QWidget *parent)
0067             : hostEdit{new QLineEdit{parent}}
0068             , portSpinBox{new QSpinBox{parent}}
0069             , useDefaultPortCheckBox{new QCheckBox{parent}}
0070             , authenticationGroup{new QButtonGroup{parent}}
0071             , userEdit{new QLineEdit{parent}}
0072             , passwordEdit{new KPasswordLineEdit{parent}}
0073             , connectionGroup{new QButtonGroup{parent}}
0074             , advancedSettings{new KCollapsibleGroupBox{parent}}
0075             , baseDnEdit{new QLineEdit{parent}}
0076             , additionalFlagsEdit{new QLineEdit{parent}}
0077             , buttonBox{new QDialogButtonBox{parent}}
0078         {
0079 #define SET_OBJECT_NAME(x) x->setObjectName(QStringLiteral(#x));
0080             SET_OBJECT_NAME(hostEdit)
0081             SET_OBJECT_NAME(portSpinBox)
0082             SET_OBJECT_NAME(useDefaultPortCheckBox)
0083             SET_OBJECT_NAME(authenticationGroup)
0084             SET_OBJECT_NAME(userEdit)
0085             SET_OBJECT_NAME(passwordEdit)
0086             SET_OBJECT_NAME(connectionGroup)
0087             SET_OBJECT_NAME(advancedSettings)
0088             SET_OBJECT_NAME(baseDnEdit)
0089             SET_OBJECT_NAME(additionalFlagsEdit)
0090             SET_OBJECT_NAME(buttonBox)
0091 #undef SET_OBJECT_NAME
0092             auto mainLayout = new QVBoxLayout{parent};
0093 
0094             auto serverWidget = new QWidget{parent};
0095             {
0096                 auto layout = new QGridLayout{serverWidget};
0097                 layout->setColumnStretch(2, 1);
0098                 int row = 0;
0099                 layout->addWidget(new QLabel{i18n("Host:")}, row, 0);
0100                 hostEdit->setToolTip(i18nc("@info:tooltip", "Enter the name or IP address of the server hosting the directory service."));
0101                 hostEdit->setClearButtonEnabled(true);
0102                 layout->addWidget(hostEdit, row, 1, 1, -1);
0103                 ++row;
0104                 layout->addWidget(new QLabel{i18n("Port:")}, row, 0);
0105                 portSpinBox->setRange(1, USHRT_MAX);
0106                 portSpinBox->setToolTip(i18nc("@info:tooltip",
0107                                               "<b>(Optional, the default is fine in most cases)</b> "
0108                                               "Pick the port number the directory service is listening on."));
0109                 layout->addWidget(portSpinBox, row, 1);
0110                 useDefaultPortCheckBox->setText(i18n("Use default"));
0111                 useDefaultPortCheckBox->setChecked(true);
0112                 layout->addWidget(useDefaultPortCheckBox, row, 2);
0113             }
0114             mainLayout->addWidget(serverWidget);
0115 
0116             auto authenticationWidget = new QGroupBox{i18n("Authentication"), parent};
0117             {
0118                 auto layout = new QVBoxLayout{authenticationWidget};
0119                 {
0120                     auto radioButton = new QRadioButton{i18n("Anonymous")};
0121                     radioButton->setToolTip(i18nc("@info:tooltip", "Use an anonymous LDAP server that does not require authentication."));
0122                     radioButton->setChecked(true);
0123                     authenticationGroup->addButton(radioButton, static_cast<int>(KeyserverAuthentication::Anonymous));
0124                     layout->addWidget(radioButton);
0125                 }
0126                 {
0127                     auto radioButton = new QRadioButton{i18n("Authenticate via Active Directory")};
0128                     if (!engineIsVersion(2, 2, 28, GpgME::GpgSMEngine)) {
0129                         radioButton->setText(i18n("Authenticate via Active Directory (requires GnuPG 2.2.28 or later)"));
0130                     }
0131                     radioButton->setToolTip(
0132                         i18nc("@info:tooltip", "On Windows, authenticate to the LDAP server using the Active Directory with the current user."));
0133                     authenticationGroup->addButton(radioButton, static_cast<int>(KeyserverAuthentication::ActiveDirectory));
0134                     layout->addWidget(radioButton);
0135                 }
0136                 {
0137                     auto radioButton = new QRadioButton{i18n("Authenticate with user and password")};
0138                     radioButton->setToolTip(i18nc("@info:tooltip", "Authenticate to the LDAP server with your LDAP credentials."));
0139                     authenticationGroup->addButton(radioButton, static_cast<int>(KeyserverAuthentication::Password));
0140                     layout->addWidget(radioButton);
0141                 }
0142 
0143                 auto credentialsWidget = new QWidget{parent};
0144                 {
0145                     auto layout = new QGridLayout{credentialsWidget};
0146                     layout->setColumnStretch(1, 1);
0147                     int row = 0;
0148                     layout->addWidget(new QLabel{i18n("User:")}, row, 0);
0149                     userEdit->setToolTip(i18nc("@info:tooltip", "Enter your LDAP user resp. Bind DN for authenticating to the LDAP server."));
0150                     userEdit->setClearButtonEnabled(true);
0151                     layout->addWidget(userEdit, row, 1);
0152                     ++row;
0153                     layout->addWidget(new QLabel{i18n("Password:")}, row, 0);
0154                     passwordEdit->setToolTip(xi18nc("@info:tooltip",
0155                                                     "Enter your password for authenticating to the LDAP server.<nl/>"
0156                                                     "<warning>The password will be saved in the clear "
0157                                                     "in a configuration file in your home directory.</warning>"));
0158                     passwordEdit->setClearButtonEnabled(true);
0159                     layout->addWidget(passwordEdit, row, 1);
0160                 }
0161                 layout->addWidget(credentialsWidget);
0162             }
0163             mainLayout->addWidget(authenticationWidget);
0164 
0165             auto securityWidget = new QGroupBox{i18n("Connection Security"), parent};
0166             if (!engineIsVersion(2, 2, 28, GpgME::GpgSMEngine)) {
0167                 securityWidget->setTitle(i18n("Connection Security (requires GnuPG 2.2.28 or later)"));
0168             }
0169             {
0170                 auto layout = new QVBoxLayout{securityWidget};
0171                 {
0172                     auto radioButton = new QRadioButton{i18n("Use default connection (probably not TLS secured)")};
0173                     radioButton->setToolTip(i18nc("@info:tooltip",
0174                                                   "Use GnuPG's default to connect to the LDAP server. "
0175                                                   "By default, GnuPG 2.3 and earlier use a plain, not TLS secured connection. "
0176                                                   "<b>(Not recommended)</b>"));
0177                     radioButton->setChecked(true);
0178                     connectionGroup->addButton(radioButton, static_cast<int>(KeyserverConnection::Default));
0179                     layout->addWidget(radioButton);
0180                 }
0181                 {
0182                     auto radioButton = new QRadioButton{i18n("Do not use a TLS secured connection")};
0183                     radioButton->setToolTip(i18nc("@info:tooltip",
0184                                                   "Use a plain, not TLS secured connection to connect to the LDAP server. "
0185                                                   "<b>(Not recommended)</b>"));
0186                     connectionGroup->addButton(radioButton, static_cast<int>(KeyserverConnection::Plain));
0187                     layout->addWidget(radioButton);
0188                 }
0189                 {
0190                     auto radioButton = new QRadioButton{i18n("Use TLS secured connection")};
0191                     radioButton->setToolTip(i18nc("@info:tooltip",
0192                                                   "Use a standard TLS secured connection (initiated with STARTTLS) "
0193                                                   "to connect to the LDAP server. "
0194                                                   "<b>(Recommended)</b>"));
0195                     connectionGroup->addButton(radioButton, static_cast<int>(KeyserverConnection::UseSTARTTLS));
0196                     layout->addWidget(radioButton);
0197                 }
0198                 {
0199                     auto radioButton = new QRadioButton{i18n("Tunnel LDAP through a TLS connection")};
0200                     radioButton->setToolTip(i18nc("@info:tooltip",
0201                                                   "Use a TLS secured connection through which the connection to the "
0202                                                   "LDAP server is tunneled. "
0203                                                   "<b>(Not recommended)</b>"));
0204                     connectionGroup->addButton(radioButton, static_cast<int>(KeyserverConnection::TunnelThroughTLS));
0205                     layout->addWidget(radioButton);
0206                 }
0207             }
0208             mainLayout->addWidget(securityWidget);
0209 
0210             advancedSettings->setTitle(i18n("Advanced Settings"));
0211             {
0212                 auto layout = new QGridLayout{advancedSettings};
0213                 layout->setColumnStretch(1, 1);
0214                 int row = 0;
0215                 layout->addWidget(new QLabel{i18n("Base DN:")}, row, 0);
0216                 baseDnEdit->setToolTip(i18nc("@info:tooltip",
0217                                              "<b>(Optional, can usually be left empty)</b> "
0218                                              "Enter the base DN for this LDAP server to limit searches "
0219                                              "to only that subtree of the directory."));
0220                 baseDnEdit->setClearButtonEnabled(true);
0221                 layout->addWidget(baseDnEdit, row, 1);
0222                 ++row;
0223                 layout->addWidget(new QLabel{i18n("Additional flags:")}, row, 0);
0224                 additionalFlagsEdit->setToolTip(i18nc("@info:tooltip",
0225                                                       "Here you can enter additional flags that are not yet (or no longer) "
0226                                                       "supported by Kleopatra. For example, older versions of GnuPG use "
0227                                                       "<code>ldaps</code> to request a TLS secured connection."));
0228                 additionalFlagsEdit->setClearButtonEnabled(true);
0229                 layout->addWidget(additionalFlagsEdit, row, 1);
0230             }
0231             mainLayout->addWidget(advancedSettings);
0232 
0233             mainLayout->addStretch(1);
0234 
0235             buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0236             QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
0237             KGuiItem::assign(okButton, KStandardGuiItem::ok());
0238             KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel());
0239             mainLayout->addWidget(buttonBox);
0240         };
0241     } ui;
0242 
0243     QString host() const
0244     {
0245         return ui.hostEdit->text().trimmed();
0246     }
0247 
0248     int port() const
0249     {
0250         return ui.useDefaultPortCheckBox->isChecked() ? -1 : ui.portSpinBox->value();
0251     }
0252 
0253     KeyserverAuthentication authentication() const
0254     {
0255         return KeyserverAuthentication{ui.authenticationGroup->checkedId()};
0256     }
0257 
0258     QString user() const
0259     {
0260         return ui.userEdit->text().trimmed();
0261     }
0262 
0263     QString password() const
0264     {
0265         return ui.passwordEdit->password(); // not trimmed
0266     }
0267 
0268     KeyserverConnection connection() const
0269     {
0270         return KeyserverConnection{ui.connectionGroup->checkedId()};
0271     }
0272 
0273     QString baseDn() const
0274     {
0275         return ui.baseDnEdit->text().trimmed();
0276     }
0277 
0278     QStringList additionalFlags() const
0279     {
0280         return transformInPlace(ui.additionalFlagsEdit->text().split(QLatin1Char{','}, Qt::SkipEmptyParts), [](const auto &flag) {
0281             return flag.trimmed();
0282         });
0283     }
0284 
0285     bool inputIsAcceptable() const
0286     {
0287         const bool hostIsSet = !host().isEmpty();
0288         const bool requiredCredentialsAreSet = authentication() != KeyserverAuthentication::Password || (!user().isEmpty() && !password().isEmpty());
0289         return hostIsSet && requiredCredentialsAreSet;
0290     }
0291 
0292     void updateWidgets()
0293     {
0294         ui.portSpinBox->setEnabled(!ui.useDefaultPortCheckBox->isChecked());
0295         if (ui.useDefaultPortCheckBox->isChecked()) {
0296             ui.portSpinBox->setValue(defaultPort(connection()));
0297         }
0298 
0299         ui.userEdit->setEnabled(authentication() == KeyserverAuthentication::Password);
0300         ui.passwordEdit->setEnabled(authentication() == KeyserverAuthentication::Password);
0301 
0302         ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(inputIsAcceptable());
0303     }
0304 
0305 public:
0306     Private(EditDirectoryServiceDialog *q)
0307         : q{q}
0308         , ui{q}
0309     {
0310         connect(ui.hostEdit, &QLineEdit::textEdited, q, [this]() {
0311             updateWidgets();
0312         });
0313         connect(ui.useDefaultPortCheckBox, &QCheckBox::toggled, q, [this]() {
0314             updateWidgets();
0315         });
0316         connect(ui.authenticationGroup, &QButtonGroup::idToggled, q, [this]() {
0317             updateWidgets();
0318         });
0319         connect(ui.userEdit, &QLineEdit::textEdited, q, [this]() {
0320             updateWidgets();
0321         });
0322         connect(ui.passwordEdit, &KPasswordLineEdit::passwordChanged, q, [this]() {
0323             updateWidgets();
0324         });
0325         connect(ui.connectionGroup, &QButtonGroup::idToggled, q, [this]() {
0326             updateWidgets();
0327         });
0328 
0329         connect(ui.buttonBox, &QDialogButtonBox::accepted, q, &EditDirectoryServiceDialog::accept);
0330         connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &EditDirectoryServiceDialog::reject);
0331 
0332         updateWidgets();
0333 
0334         restoreLayout();
0335     }
0336 
0337     ~Private()
0338     {
0339         saveLayout();
0340     }
0341 
0342     void setKeyserver(const KeyserverConfig &keyserver)
0343     {
0344         ui.hostEdit->setText(keyserver.host());
0345         ui.useDefaultPortCheckBox->setChecked(keyserver.port() == -1);
0346         ui.portSpinBox->setValue(keyserver.port() == -1 ? defaultPort(keyserver.connection()) : keyserver.port());
0347         ui.authenticationGroup->button(static_cast<int>(keyserver.authentication()))->setChecked(true);
0348         ui.userEdit->setText(keyserver.user());
0349         ui.passwordEdit->setPassword(keyserver.password());
0350         ui.connectionGroup->button(static_cast<int>(keyserver.connection()))->setChecked(true);
0351         ui.baseDnEdit->setText(keyserver.ldapBaseDn());
0352         ui.additionalFlagsEdit->setText(keyserver.additionalFlags().join(QLatin1Char{','}));
0353 
0354         ui.advancedSettings->setExpanded(!keyserver.ldapBaseDn().isEmpty() || !keyserver.additionalFlags().empty());
0355         updateWidgets();
0356     }
0357 
0358     KeyserverConfig keyserver() const
0359     {
0360         KeyserverConfig keyserver;
0361         keyserver.setHost(host());
0362         keyserver.setPort(port());
0363         keyserver.setAuthentication(authentication());
0364         keyserver.setUser(user());
0365         keyserver.setPassword(password());
0366         keyserver.setConnection(connection());
0367         keyserver.setLdapBaseDn(baseDn());
0368         keyserver.setAdditionalFlags(additionalFlags());
0369 
0370         return keyserver;
0371     }
0372 
0373 private:
0374     void saveLayout()
0375     {
0376         KConfigGroup configGroup{KSharedConfig::openStateConfig(), QLatin1StringView("EditDirectoryServiceDialog")};
0377         configGroup.writeEntry("Size", q->size());
0378         configGroup.sync();
0379     }
0380 
0381     void restoreLayout()
0382     {
0383         const KConfigGroup configGroup{KSharedConfig::openStateConfig(), QLatin1StringView("EditDirectoryServiceDialog")};
0384         const auto size = configGroup.readEntry("Size", QSize{});
0385         if (size.isValid()) {
0386             q->resize(size);
0387         }
0388     }
0389 };
0390 
0391 EditDirectoryServiceDialog::EditDirectoryServiceDialog(QWidget *parent, Qt::WindowFlags f)
0392     : QDialog{parent, f}
0393     , d{std::make_unique<Private>(this)}
0394 {
0395     setWindowTitle(i18nc("@title:window", "Edit Directory Service"));
0396 }
0397 
0398 EditDirectoryServiceDialog::~EditDirectoryServiceDialog() = default;
0399 
0400 void EditDirectoryServiceDialog::setKeyserver(const KeyserverConfig &keyserver)
0401 {
0402     d->setKeyserver(keyserver);
0403 }
0404 
0405 KeyserverConfig EditDirectoryServiceDialog::keyserver() const
0406 {
0407     return d->keyserver();
0408 }
0409 
0410 #include "moc_editdirectoryservicedialog.cpp"