Libkleo

directoryserviceswidget.cpp
1 /*
2  ui/directoryserviceswidget.cpp
3 
4  This file is part of libkleopatra, the KDE keymanagement library
5  SPDX-FileCopyrightText: 2001, 2002, 2004 Klarälvdalens Datakonsult AB
6  SPDX-FileCopyrightText: 2017 Bundesamnt für Sicherheit in der Informationstechnik
7  SPDX-FileCopyrightText: 2021 g10 Code GmbH
8  SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
9 
10  SPDX-License-Identifier: GPL-2.0-or-later
11 */
12 
13 #include <config-libkleo.h>
14 
15 #include "directoryserviceswidget.h"
16 
17 #include "editdirectoryservicedialog.h"
18 
19 #include <libkleo/gnupg.h>
20 #include <libkleo/keyserverconfig.h>
21 
22 #include <kleo_ui_debug.h>
23 
24 #include <KLocalizedString>
25 
26 #include <QListView>
27 #include <QMenu>
28 #include <QPointer>
29 #include <QPushButton>
30 #include <QToolButton>
31 #include <QVBoxLayout>
32 
33 using namespace Kleo;
34 
35 namespace
36 {
37 
38 bool activeDirectoryIsSupported()
39 {
40  return engineIsVersion(2, 2, 28, GpgME::GpgSMEngine);
41 }
42 
43 bool isStandardActiveDirectory(const KeyserverConfig &keyserver)
44 {
45  return (keyserver.authentication() == KeyserverAuthentication::ActiveDirectory) && keyserver.host().isEmpty();
46 }
47 
48 bool keyserverIsEditable(const KeyserverConfig &keyserver)
49 {
50  // standard AD is not editable
51  return !isStandardActiveDirectory(keyserver);
52 }
53 
54 class KeyserverModel : public QAbstractListModel
55 {
56  Q_OBJECT
57 public:
58  explicit KeyserverModel(QObject *parent = nullptr)
59  : QAbstractListModel{parent}
60  {
61  }
62 
63  void setKeyservers(const std::vector<KeyserverConfig> &servers)
64  {
65  clear();
66  beginInsertRows(QModelIndex(), 0, servers.size() - 1);
67  m_items = servers;
68  endInsertRows();
69  }
70 
71  void addKeyserver(const KeyserverConfig &keyserver)
72  {
73  const auto row = m_items.size();
74  beginInsertRows(QModelIndex(), row, row);
75  m_items.push_back(keyserver);
76  endInsertRows();
77  }
78 
79  KeyserverConfig getKeyserver(unsigned int id)
80  {
81  if (id >= m_items.size()) {
82  qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id;
83  return {};
84  }
85 
86  return m_items[id];
87  }
88 
89  void updateKeyserver(unsigned int id, const KeyserverConfig &keyserver)
90  {
91  if (id >= m_items.size()) {
92  qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id;
93  return;
94  }
95 
96  m_items[id] = keyserver;
97  Q_EMIT dataChanged(index(id), index(id));
98  }
99 
100  void deleteKeyserver(unsigned int id)
101  {
102  if (id >= m_items.size()) {
103  qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id;
104  return;
105  }
106 
107  beginRemoveRows(QModelIndex(), id, id);
108  m_items.erase(m_items.begin() + id);
109  endRemoveRows();
110  }
111 
112  void clear()
113  {
114  if (m_items.empty()) {
115  return;
116  }
117  beginRemoveRows(QModelIndex(), 0, m_items.size() - 1);
118  m_items.clear();
119  endRemoveRows();
120  }
121 
122  int rowCount(const QModelIndex & = QModelIndex()) const override
123  {
124  return m_items.size();
125  }
126 
127  QVariant data(const QModelIndex &index, int role) const override
128  {
129  if (!index.isValid()) {
130  return {};
131  }
132  switch (role) {
133  case Qt::DisplayRole:
134  case Qt::EditRole: {
135  const auto keyserver = m_items[index.row()];
136  return isStandardActiveDirectory(keyserver) ? i18n("Active Directory") : keyserver.host();
137  }
138  }
139  return {};
140  }
141 
142  bool hasActiveDirectory()
143  {
144  // check whether any of the model items represents an Active Directory keyserver
145  return std::any_of(std::cbegin(m_items), std::cend(m_items), isStandardActiveDirectory);
146  }
147 
148 private:
150 
151 private:
152  std::vector<KeyserverConfig> m_items;
153 };
154 }
155 
156 class DirectoryServicesWidget::Private
157 {
158  DirectoryServicesWidget *const q;
159 
160  struct {
161  QListView *keyserverList = nullptr;
162  QToolButton *newButton = nullptr;
163  QAction *addActiveDirectoryAction = nullptr;
164  QAction *addLdapServerAction = nullptr;
165  QPushButton *editButton = nullptr;
166  QPushButton *deleteButton = nullptr;
167  } ui;
168  KeyserverModel *keyserverModel = nullptr;
169  bool readOnly = false;
170 
171 public:
172  Private(DirectoryServicesWidget *qq)
173  : q(qq)
174  {
175  auto mainLayout = new QVBoxLayout{q};
176 
177  auto gridLayout = new QGridLayout{};
178  gridLayout->setColumnStretch(0, 1);
179  gridLayout->setRowStretch(1, 1);
180 
181  keyserverModel = new KeyserverModel{q};
182  ui.keyserverList = new QListView();
183  ui.keyserverList->setModel(keyserverModel);
184  ui.keyserverList->setModelColumn(0);
185  ui.keyserverList->setSelectionBehavior(QAbstractItemView::SelectRows);
186  ui.keyserverList->setSelectionMode(QAbstractItemView::SingleSelection);
187  ui.keyserverList->setWhatsThis(i18nc("@info:whatsthis", "This is a list of all directory services that are configured for use with X.509."));
188  gridLayout->addWidget(ui.keyserverList, 1, 0);
189 
190  auto groupsButtonLayout = new QVBoxLayout();
191 
192  auto menu = new QMenu{q};
193  ui.addActiveDirectoryAction = menu->addAction(i18n("Active Directory"), [this]() {
194  addActiveDirectory();
195  });
196  ui.addActiveDirectoryAction->setToolTip(i18nc("@info:tooltip",
197  "Click to use a directory service running on your Active Directory. "
198  "This works only on Windows and requires GnuPG 2.2.28 or later."));
199  ui.addActiveDirectoryAction->setEnabled(activeDirectoryIsSupported());
200  ui.addLdapServerAction = menu->addAction(i18n("LDAP Server"), [this]() {
201  addLdapServer();
202  });
203  ui.addLdapServerAction->setToolTip(i18nc("@info:tooltip", "Click to add a directory service provided by an LDAP server."));
204  ui.newButton = new QToolButton{q};
205  ui.newButton->setText(i18n("Add"));
206  ui.newButton->setToolTip(i18nc("@info:tooltip", "Click to add a directory service."));
207  ui.newButton->setWhatsThis(i18nc("@info:whatsthis",
208  "Click this button to add a directory service to the list of services. "
209  "The change will only take effect once you acknowledge the configuration dialog."));
210  ui.newButton->setToolButtonStyle(Qt::ToolButtonTextOnly);
211  ui.newButton->setPopupMode(QToolButton::InstantPopup);
212  ui.newButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); // expand horizontally like the QPushButtons
213  ui.newButton->setMenu(menu);
214  groupsButtonLayout->addWidget(ui.newButton);
215 
216  ui.editButton = new QPushButton(i18n("Edit"));
217  ui.editButton->setToolTip(i18nc("@info:tooltip", "Click to edit the selected service."));
218  ui.editButton->setWhatsThis(i18nc("@info:whatsthis",
219  "Click this button to edit the settings of the currently selected directory service. "
220  "The changes will only take effect once you acknowledge the configuration dialog."));
221  ui.editButton->setEnabled(false);
222  groupsButtonLayout->addWidget(ui.editButton);
223 
224  ui.deleteButton = new QPushButton(i18n("Delete"));
225  ui.deleteButton->setToolTip(i18nc("@info:tooltip", "Click to remove the selected service."));
226  ui.deleteButton->setWhatsThis(i18nc("@info:whatsthis",
227  "Click this button to remove the currently selected directory service. "
228  "The change will only take effect once you acknowledge the configuration dialog."));
229  ui.deleteButton->setEnabled(false);
230  groupsButtonLayout->addWidget(ui.deleteButton);
231 
232  groupsButtonLayout->addStretch(1);
233 
234  gridLayout->addLayout(groupsButtonLayout, 1, 1);
235 
236  mainLayout->addLayout(gridLayout, /*stretch=*/1);
237 
238  connect(keyserverModel, &QAbstractItemModel::dataChanged, q, [this]() {
239  modelChanged();
240  });
241  connect(keyserverModel, &QAbstractItemModel::rowsInserted, q, [this]() {
242  modelChanged();
243  });
244  connect(keyserverModel, &QAbstractItemModel::rowsRemoved, q, [this]() {
245  modelChanged();
246  });
247  connect(ui.keyserverList->selectionModel(), &QItemSelectionModel::selectionChanged, q, [this]() {
248  selectionChanged();
249  });
250  connect(ui.keyserverList, &QListView::doubleClicked, q, [this](const QModelIndex &index) {
251  if (!readOnly) {
252  editKeyserver(index);
253  }
254  });
255  connect(ui.editButton, &QPushButton::clicked, q, [this]() {
256  editKeyserver();
257  });
258  connect(ui.deleteButton, &QPushButton::clicked, q, [this]() {
259  deleteKeyserver();
260  });
261  }
262 
263  void setReadOnly(bool ro)
264  {
265  readOnly = ro;
266  updateActions();
267  }
268 
269  void setKeyservers(const std::vector<KeyserverConfig> &servers)
270  {
271  keyserverModel->setKeyservers(servers);
272  }
273 
274  std::vector<KeyserverConfig> keyservers() const
275  {
276  std::vector<KeyserverConfig> result;
277  result.reserve(keyserverModel->rowCount());
278  for (int row = 0; row < keyserverModel->rowCount(); ++row) {
279  result.push_back(keyserverModel->getKeyserver(row));
280  }
281  return result;
282  }
283 
284  void clear()
285  {
286  if (keyserverModel->rowCount() == 0) {
287  return;
288  }
289  keyserverModel->clear();
290  }
291 
292 private:
293  auto selectedIndex()
294  {
295  const auto indexes = ui.keyserverList->selectionModel()->selectedRows();
296  return indexes.empty() ? QModelIndex() : indexes[0];
297  }
298 
299  void modelChanged()
300  {
301  updateActions();
302  Q_EMIT q->changed();
303  }
304 
305  void selectionChanged()
306  {
307  updateActions();
308  }
309 
310  void updateActions()
311  {
312  const auto index = selectedIndex();
313  ui.newButton->setEnabled(!readOnly);
314  ui.addActiveDirectoryAction->setEnabled(activeDirectoryIsSupported() && !keyserverModel->hasActiveDirectory());
315  ui.editButton->setEnabled(!readOnly && index.isValid() && keyserverIsEditable(keyserverModel->getKeyserver(index.row())));
316  ui.deleteButton->setEnabled(!readOnly && index.isValid());
317  }
318 
319  void handleEditKeyserverDialogResult(const int id, const EditDirectoryServiceDialog *dialog)
320  {
321  if (id >= 0) {
322  keyserverModel->updateKeyserver(id, dialog->keyserver());
323  } else {
324  keyserverModel->addKeyserver(dialog->keyserver());
325  }
326  }
327 
328  void showEditKeyserverDialog(const int id, const KeyserverConfig &keyserver, const QString &windowTitle)
329  {
330  QPointer<EditDirectoryServiceDialog> dialog{new EditDirectoryServiceDialog{q}};
331  dialog->setAttribute(Qt::WA_DeleteOnClose);
332  dialog->setWindowModality(Qt::WindowModal);
333  dialog->setWindowTitle(windowTitle);
334  dialog->setKeyserver(keyserver);
335 
336  connect(dialog, &QDialog::accepted, q, [dialog, id, this] {
337  handleEditKeyserverDialogResult(id, dialog);
338  });
339 
340  dialog->show();
341  }
342 
343  void addActiveDirectory()
344  {
345  KeyserverConfig keyserver;
346  keyserver.setAuthentication(KeyserverAuthentication::ActiveDirectory);
347  keyserverModel->addKeyserver(keyserver);
348  }
349 
350  void addLdapServer()
351  {
352  showEditKeyserverDialog(-1, {}, i18nc("@title:window", "LDAP Directory Service"));
353  }
354 
355  void editKeyserver(const QModelIndex &index = {})
356  {
357  const auto serverIndex = index.isValid() ? index : selectedIndex();
358  if (!serverIndex.isValid()) {
359  qCDebug(KLEO_UI_LOG) << __func__ << "selection is empty";
360  return;
361  }
362  const auto id = serverIndex.row();
363  const KeyserverConfig keyserver = keyserverModel->getKeyserver(id);
364  if (!keyserverIsEditable(keyserver)) {
365  qCDebug(KLEO_UI_LOG) << __func__ << "selected keyserver (id:" << id << ") cannot be modified";
366  return;
367  }
368 
369  showEditKeyserverDialog(id, keyserver, i18nc("@title:window", "LDAP Directory Service"));
370  }
371 
372  void deleteKeyserver()
373  {
374  const QModelIndex serverIndex = selectedIndex();
375  if (!serverIndex.isValid()) {
376  qCDebug(KLEO_UI_LOG) << __func__ << "selection is empty";
377  return;
378  }
379  keyserverModel->deleteKeyserver(serverIndex.row());
380  }
381 };
382 
383 DirectoryServicesWidget::DirectoryServicesWidget(QWidget *parent)
384  : QWidget{parent}
385  , d{std::make_unique<Private>(this)}
386 {
387 }
388 
389 DirectoryServicesWidget::~DirectoryServicesWidget() = default;
390 
391 void DirectoryServicesWidget::setKeyservers(const std::vector<KeyserverConfig> &servers)
392 {
393  d->setKeyservers(servers);
394 }
395 
396 std::vector<KeyserverConfig> DirectoryServicesWidget::keyservers() const
397 {
398  return d->keyservers();
399 }
400 
401 void DirectoryServicesWidget::setReadOnly(bool readOnly)
402 {
403  d->setReadOnly(readOnly);
404 }
405 
406 void DirectoryServicesWidget::clear()
407 {
408  d->clear();
409 }
410 
411 #include "directoryserviceswidget.moc"
412 
413 #include "moc_directoryserviceswidget.cpp"
void doubleClicked(const QModelIndex &index)
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
DisplayRole
WindowModal
void clicked(bool checked)
QAction * addAction(const QString &text)
void setColumnStretch(int column, int stretch)
QString i18n(const char *text, const TYPE &arg...)
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles)
bool isEmpty() const const
bool isValid() const const
int row() const const
QAction * clear(const QObject *recvr, const char *slot, QObject *parent)
void rowsInserted(const QModelIndex &parent, int first, int last)
void setToolTip(const QString &tip)
void rowsRemoved(const QModelIndex &parent, int first, int last)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
ToolButtonTextOnly
virtual bool setData(const QModelIndex &index, const QVariant &value, int role)
void setText(const QString &text)
WA_DeleteOnClose
void accepted()
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.