Libkleo

editdirectoryservicedialog.cpp
1/*
2 ui/editdirectoryservicedialog.cpp
3
4 This file is part of libkleopatra, the KDE keymanagement library
5 SPDX-FileCopyrightText: 2021 g10 Code GmbH
6 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
7
8 SPDX-License-Identifier: GPL-2.0-or-later
9*/
10
11#include <config-libkleo.h>
12
13#include "editdirectoryservicedialog.h"
14
15#include <libkleo/algorithm.h>
16#include <libkleo/gnupg.h>
17#include <libkleo/keyserverconfig.h>
18
19#include <KCollapsibleGroupBox>
20#include <KColorScheme>
21#include <KConfigGroup>
22#include <KGuiItem>
23#include <KLocalizedString>
24#include <KPasswordLineEdit>
25#include <KSharedConfig>
26#include <KStandardGuiItem>
27
28#include <QButtonGroup>
29#include <QCheckBox>
30#include <QDialogButtonBox>
31#include <QGridLayout>
32#include <QGroupBox>
33#include <QLabel>
34#include <QLineEdit>
35#include <QPushButton>
36#include <QRadioButton>
37#include <QSpinBox>
38#include <QVBoxLayout>
39
40using namespace Kleo;
41
42namespace
43{
44int defaultPort(KeyserverConnection connection)
45{
46 return connection == KeyserverConnection::TunnelThroughTLS ? 636 : 389;
47}
48}
49
50class EditDirectoryServiceDialog::Private
51{
52 EditDirectoryServiceDialog *const q;
53
54 struct Ui {
55 QLineEdit *hostEdit = nullptr;
56 QSpinBox *portSpinBox = nullptr;
57 QCheckBox *useDefaultPortCheckBox = nullptr;
58 QButtonGroup *authenticationGroup = nullptr;
59 QLineEdit *userEdit = nullptr;
60 KPasswordLineEdit *passwordEdit = nullptr;
61 QButtonGroup *connectionGroup = nullptr;
62 KCollapsibleGroupBox *advancedSettings = nullptr;
63 QLineEdit *baseDnEdit = nullptr;
64 QLineEdit *additionalFlagsEdit = nullptr;
65 QDialogButtonBox *buttonBox = nullptr;
66 QLabel *invalidHostLabel = nullptr;
67
68 Ui(QWidget *parent)
69 : hostEdit{new QLineEdit{parent}}
70 , portSpinBox{new QSpinBox{parent}}
71 , useDefaultPortCheckBox{new QCheckBox{parent}}
72 , authenticationGroup{new QButtonGroup{parent}}
73 , userEdit{new QLineEdit{parent}}
74 , passwordEdit{new KPasswordLineEdit{parent}}
75 , connectionGroup{new QButtonGroup{parent}}
76 , advancedSettings{new KCollapsibleGroupBox{parent}}
77 , baseDnEdit{new QLineEdit{parent}}
78 , additionalFlagsEdit{new QLineEdit{parent}}
79 , buttonBox{new QDialogButtonBox{parent}}
80 , invalidHostLabel{new QLabel{parent}}
81 {
82#define SET_OBJECT_NAME(x) x->setObjectName(QStringLiteral(#x));
83 SET_OBJECT_NAME(hostEdit)
84 SET_OBJECT_NAME(portSpinBox)
85 SET_OBJECT_NAME(useDefaultPortCheckBox)
86 SET_OBJECT_NAME(authenticationGroup)
87 SET_OBJECT_NAME(userEdit)
88 SET_OBJECT_NAME(passwordEdit)
89 SET_OBJECT_NAME(connectionGroup)
90 SET_OBJECT_NAME(advancedSettings)
91 SET_OBJECT_NAME(baseDnEdit)
92 SET_OBJECT_NAME(additionalFlagsEdit)
93 SET_OBJECT_NAME(buttonBox)
94#undef SET_OBJECT_NAME
95 auto mainLayout = new QVBoxLayout{parent};
96
97 auto serverWidget = new QWidget{parent};
98 {
99 auto layout = new QGridLayout{serverWidget};
100 layout->setColumnStretch(2, 1);
101 int row = 0;
102 layout->addWidget(new QLabel{i18n("Host:")}, row, 0);
103 hostEdit->setToolTip(i18nc("@info:tooltip", "Enter the name or IP address of the server hosting the directory service."));
104 hostEdit->setClearButtonEnabled(true);
105 layout->addWidget(hostEdit, row, 1, 1, -1);
106 ++row;
110 palette.setBrush(QPalette::WindowText, colors.foreground(KColorScheme::NegativeText));
111 invalidHostLabel->setPalette(palette);
112 invalidHostLabel->setText(i18nc("@info:label", "Error: Enter a hostname in the correct format, like ldap.example.com."));
113 invalidHostLabel->setVisible(false);
114 layout->addWidget(invalidHostLabel, row, 1, 1, 2);
115 ++row;
116 layout->addWidget(new QLabel{i18n("Port:")}, row, 0);
117 portSpinBox->setRange(1, USHRT_MAX);
118 portSpinBox->setToolTip(i18nc("@info:tooltip",
119 "<b>(Optional, the default is fine in most cases)</b> "
120 "Pick the port number the directory service is listening on."));
121 layout->addWidget(portSpinBox, row, 1);
122 useDefaultPortCheckBox->setText(i18n("Use default"));
123 useDefaultPortCheckBox->setChecked(true);
124 layout->addWidget(useDefaultPortCheckBox, row, 2);
125 }
126 mainLayout->addWidget(serverWidget);
127
128 auto authenticationWidget = new QGroupBox{i18n("Authentication"), parent};
129 {
130 auto layout = new QVBoxLayout{authenticationWidget};
131 {
132 auto radioButton = new QRadioButton{i18n("Anonymous")};
133 radioButton->setToolTip(i18nc("@info:tooltip", "Use an anonymous LDAP server that does not require authentication."));
134 radioButton->setChecked(true);
135 authenticationGroup->addButton(radioButton, static_cast<int>(KeyserverAuthentication::Anonymous));
136 layout->addWidget(radioButton);
137 }
138 {
139 auto radioButton = new QRadioButton{i18n("Authenticate via Active Directory")};
140 if (!engineIsVersion(2, 2, 28, GpgME::GpgSMEngine)) {
141 radioButton->setText(i18n("Authenticate via Active Directory (requires GnuPG 2.2.28 or later)"));
142 }
143 radioButton->setToolTip(
144 i18nc("@info:tooltip", "On Windows, authenticate to the LDAP server using the Active Directory with the current user."));
145 authenticationGroup->addButton(radioButton, static_cast<int>(KeyserverAuthentication::ActiveDirectory));
146 layout->addWidget(radioButton);
147 }
148 {
149 auto radioButton = new QRadioButton{i18n("Authenticate with user and password")};
150 radioButton->setToolTip(i18nc("@info:tooltip", "Authenticate to the LDAP server with your LDAP credentials."));
151 authenticationGroup->addButton(radioButton, static_cast<int>(KeyserverAuthentication::Password));
152 layout->addWidget(radioButton);
153 }
154
155 auto credentialsWidget = new QWidget{parent};
156 {
157 auto layout = new QGridLayout{credentialsWidget};
158 layout->setColumnStretch(1, 1);
159 int row = 0;
160 layout->addWidget(new QLabel{i18n("User:")}, row, 0);
161 userEdit->setToolTip(i18nc("@info:tooltip", "Enter your LDAP user resp. Bind DN for authenticating to the LDAP server."));
162 userEdit->setClearButtonEnabled(true);
163 layout->addWidget(userEdit, row, 1);
164 ++row;
165 layout->addWidget(new QLabel{i18n("Password:")}, row, 0);
166 passwordEdit->setToolTip(xi18nc("@info:tooltip",
167 "Enter your password for authenticating to the LDAP server.<nl/>"
168 "<warning>The password will be saved in the clear "
169 "in a configuration file in your home directory.</warning>"));
170 passwordEdit->setClearButtonEnabled(true);
171 layout->addWidget(passwordEdit, row, 1);
172 }
173 layout->addWidget(credentialsWidget);
174 }
175 mainLayout->addWidget(authenticationWidget);
176
177 auto securityWidget = new QGroupBox{i18n("Connection Security"), parent};
178 if (!engineIsVersion(2, 2, 28, GpgME::GpgSMEngine)) {
179 securityWidget->setTitle(i18n("Connection Security (requires GnuPG 2.2.28 or later)"));
180 }
181 {
182 auto layout = new QVBoxLayout{securityWidget};
183 {
184 auto radioButton = new QRadioButton{i18n("Use default connection (probably not TLS secured)")};
185 radioButton->setToolTip(i18nc("@info:tooltip",
186 "Use GnuPG's default to connect to the LDAP server. "
187 "By default, GnuPG 2.3 and earlier use a plain, not TLS secured connection. "
188 "<b>(Not recommended)</b>"));
189 radioButton->setChecked(true);
190 connectionGroup->addButton(radioButton, static_cast<int>(KeyserverConnection::Default));
191 layout->addWidget(radioButton);
192 }
193 {
194 auto radioButton = new QRadioButton{i18n("Do not use a TLS secured connection")};
195 radioButton->setToolTip(i18nc("@info:tooltip",
196 "Use a plain, not TLS secured connection to connect to the LDAP server. "
197 "<b>(Not recommended)</b>"));
198 connectionGroup->addButton(radioButton, static_cast<int>(KeyserverConnection::Plain));
199 layout->addWidget(radioButton);
200 }
201 {
202 auto radioButton = new QRadioButton{i18n("Use TLS secured connection")};
203 radioButton->setToolTip(i18nc("@info:tooltip",
204 "Use a standard TLS secured connection (initiated with STARTTLS) "
205 "to connect to the LDAP server. "
206 "<b>(Recommended)</b>"));
207 connectionGroup->addButton(radioButton, static_cast<int>(KeyserverConnection::UseSTARTTLS));
208 layout->addWidget(radioButton);
209 }
210 {
211 auto radioButton = new QRadioButton{i18n("Tunnel LDAP through a TLS connection")};
212 radioButton->setToolTip(i18nc("@info:tooltip",
213 "Use a TLS secured connection through which the connection to the "
214 "LDAP server is tunneled. "
215 "<b>(Not recommended)</b>"));
216 connectionGroup->addButton(radioButton, static_cast<int>(KeyserverConnection::TunnelThroughTLS));
217 layout->addWidget(radioButton);
218 }
219 }
220 mainLayout->addWidget(securityWidget);
221
222 advancedSettings->setTitle(i18n("Advanced Settings"));
223 {
224 auto layout = new QGridLayout{advancedSettings};
225 layout->setColumnStretch(1, 1);
226 int row = 0;
227 layout->addWidget(new QLabel{i18n("Base DN:")}, row, 0);
228 baseDnEdit->setToolTip(i18nc("@info:tooltip",
229 "<b>(Optional, can usually be left empty)</b> "
230 "Enter the base DN for this LDAP server to limit searches "
231 "to only that subtree of the directory."));
232 baseDnEdit->setClearButtonEnabled(true);
233 layout->addWidget(baseDnEdit, row, 1);
234 ++row;
235 layout->addWidget(new QLabel{i18n("Additional flags:")}, row, 0);
236 additionalFlagsEdit->setToolTip(i18nc("@info:tooltip",
237 "Here you can enter additional flags that are not yet (or no longer) "
238 "supported by Kleopatra. For example, older versions of GnuPG use "
239 "<code>ldaps</code> to request a TLS secured connection."));
240 additionalFlagsEdit->setClearButtonEnabled(true);
241 layout->addWidget(additionalFlagsEdit, row, 1);
242 }
243 mainLayout->addWidget(advancedSettings);
244
245 mainLayout->addStretch(1);
246
248 QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
251 mainLayout->addWidget(buttonBox);
252 };
253 } ui;
254
255 QString host() const
256 {
257 return ui.hostEdit->text().trimmed();
258 }
259
260 int port() const
261 {
262 return ui.useDefaultPortCheckBox->isChecked() ? -1 : ui.portSpinBox->value();
263 }
264
265 KeyserverAuthentication authentication() const
266 {
267 return KeyserverAuthentication{ui.authenticationGroup->checkedId()};
268 }
269
270 QString user() const
271 {
272 return ui.userEdit->text().trimmed();
273 }
274
275 QString password() const
276 {
277 return ui.passwordEdit->password(); // not trimmed
278 }
279
280 KeyserverConnection connection() const
281 {
282 return KeyserverConnection{ui.connectionGroup->checkedId()};
283 }
284
285 QString baseDn() const
286 {
287 return ui.baseDnEdit->text().trimmed();
288 }
289
290 QStringList additionalFlags() const
291 {
292 return transformInPlace(ui.additionalFlagsEdit->text().split(QLatin1Char{','}, Qt::SkipEmptyParts), [](const auto &flag) {
293 return flag.trimmed();
294 });
295 }
296
297 bool inputIsAcceptable() const
298 {
299 const bool hostIsSet = !host().isEmpty();
300 QUrl url;
301 url.setHost(host());
302 ui.invalidHostLabel->setVisible(!url.isValid());
303 const bool requiredCredentialsAreSet = authentication() != KeyserverAuthentication::Password || (!user().isEmpty() && !password().isEmpty());
304 return hostIsSet && requiredCredentialsAreSet && url.isValid();
305 }
306
307 void updateWidgets()
308 {
309 ui.portSpinBox->setEnabled(!ui.useDefaultPortCheckBox->isChecked());
310 if (ui.useDefaultPortCheckBox->isChecked()) {
311 ui.portSpinBox->setValue(defaultPort(connection()));
312 }
313
314 ui.userEdit->setEnabled(authentication() == KeyserverAuthentication::Password);
315 ui.passwordEdit->setEnabled(authentication() == KeyserverAuthentication::Password);
316
317 ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(inputIsAcceptable());
318 }
319
320public:
321 Private(EditDirectoryServiceDialog *q)
322 : q{q}
323 , ui{q}
324 {
325 connect(ui.hostEdit, &QLineEdit::textEdited, q, [this]() {
326 updateWidgets();
327 });
328 connect(ui.useDefaultPortCheckBox, &QCheckBox::toggled, q, [this]() {
329 updateWidgets();
330 });
331 connect(ui.authenticationGroup, &QButtonGroup::idToggled, q, [this]() {
332 updateWidgets();
333 });
334 connect(ui.userEdit, &QLineEdit::textEdited, q, [this]() {
335 updateWidgets();
336 });
337 connect(ui.passwordEdit, &KPasswordLineEdit::passwordChanged, q, [this]() {
338 updateWidgets();
339 });
340 connect(ui.connectionGroup, &QButtonGroup::idToggled, q, [this]() {
341 updateWidgets();
342 });
343
346
347 updateWidgets();
348
349 restoreLayout();
350 }
351
352 ~Private()
353 {
354 saveLayout();
355 }
356
357 void setKeyserver(const KeyserverConfig &keyserver)
358 {
359 ui.hostEdit->setText(keyserver.host());
360 ui.useDefaultPortCheckBox->setChecked(keyserver.port() == -1);
361 ui.portSpinBox->setValue(keyserver.port() == -1 ? defaultPort(keyserver.connection()) : keyserver.port());
362 ui.authenticationGroup->button(static_cast<int>(keyserver.authentication()))->setChecked(true);
363 ui.userEdit->setText(keyserver.user());
364 ui.passwordEdit->setPassword(keyserver.password());
365 ui.connectionGroup->button(static_cast<int>(keyserver.connection()))->setChecked(true);
366 ui.baseDnEdit->setText(keyserver.ldapBaseDn());
367 ui.additionalFlagsEdit->setText(keyserver.additionalFlags().join(QLatin1Char{','}));
368
369 ui.advancedSettings->setExpanded(!keyserver.ldapBaseDn().isEmpty() || !keyserver.additionalFlags().empty());
370 updateWidgets();
371 }
372
373 KeyserverConfig keyserver() const
374 {
375 KeyserverConfig keyserver;
376 keyserver.setHost(host());
377 keyserver.setPort(port());
378 keyserver.setAuthentication(authentication());
379 keyserver.setUser(user());
380 keyserver.setPassword(password());
381 keyserver.setConnection(connection());
382 keyserver.setLdapBaseDn(baseDn());
383 keyserver.setAdditionalFlags(additionalFlags());
384
385 return keyserver;
386 }
387
388private:
389 void saveLayout()
390 {
391 KConfigGroup configGroup{KSharedConfig::openStateConfig(), QLatin1StringView("EditDirectoryServiceDialog")};
392 configGroup.writeEntry("Size", q->size());
393 configGroup.sync();
394 }
395
396 void restoreLayout()
397 {
398 const KConfigGroup configGroup{KSharedConfig::openStateConfig(), QLatin1StringView("EditDirectoryServiceDialog")};
399 const auto size = configGroup.readEntry("Size", QSize{});
400 if (size.isValid()) {
401 q->resize(size);
402 }
403 }
404};
405
406EditDirectoryServiceDialog::EditDirectoryServiceDialog(QWidget *parent, Qt::WindowFlags f)
407 : QDialog{parent, f}
408 , d{std::make_unique<Private>(this)}
409{
410 setWindowTitle(i18nc("@title:window", "Edit Directory Service"));
411}
412
413EditDirectoryServiceDialog::~EditDirectoryServiceDialog() = default;
414
415void EditDirectoryServiceDialog::setKeyserver(const KeyserverConfig &keyserver)
416{
417 d->setKeyserver(keyserver);
418}
419
420KeyserverConfig EditDirectoryServiceDialog::keyserver() const
421{
422 return d->keyserver();
423}
424
425#include "moc_editdirectoryservicedialog.cpp"
void setTitle(const QString &title)
void setExpanded(bool expanded)
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
QString readEntry(const char *key, const char *aDefault=nullptr) const
static void assign(QPushButton *button, const KGuiItem &item)
QString password() const
void setPassword(const QString &password)
void setClearButtonEnabled(bool clear)
static KSharedConfig::Ptr openStateConfig(const QString &fileName=QString())
QString xi18nc(const char *context, const char *text, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
KGuiItem cancel()
void setChecked(bool)
void setText(const QString &text)
void toggled(bool checked)
void addButton(QAbstractButton *button, int id)
QAbstractButton * button(int id) const const
int checkedId() const const
void idToggled(int id, bool checked)
virtual void accept()
virtual void reject()
QPushButton * button(StandardButton which) const const
void setStandardButtons(StandardButtons buttons)
void setText(const QString &)
void addWidget(QWidget *w)
void setClearButtonEnabled(bool enable)
void textEdited(const QString &text)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
void setBrush(ColorGroup group, ColorRole role, const QBrush &brush)
void setRange(int minimum, int maximum)
bool isEmpty() const const
SkipEmptyParts
typedef WindowFlags
bool isValid() const const
void setHost(const QString &host, ParsingMode mode)
void setEnabled(bool)
QLayout * layout() const const
void setToolTip(const QString &)
virtual void setVisible(bool visible)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:29:01 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.