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

KDE's Doxygen guidelines are available online.