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"