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

KDE's Doxygen guidelines are available online.