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 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
148private:
150
151private:
152 std::vector<KeyserverConfig> m_items;
153};
154}
155
156class 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
171public:
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
292private:
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}};
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
383DirectoryServicesWidget::DirectoryServicesWidget(QWidget *parent)
384 : QWidget{parent}
385 , d{std::make_unique<Private>(this)}
386{
387}
388
389DirectoryServicesWidget::~DirectoryServicesWidget() = default;
390
391void DirectoryServicesWidget::setKeyservers(const std::vector<KeyserverConfig> &servers)
392{
393 d->setKeyservers(servers);
394}
395
396std::vector<KeyserverConfig> DirectoryServicesWidget::keyservers() const
397{
398 return d->keyservers();
399}
400
401void DirectoryServicesWidget::setReadOnly(bool readOnly)
402{
403 d->setReadOnly(readOnly);
404}
405
406void DirectoryServicesWidget::clear()
407{
408 d->clear();
409}
410
411#include "directoryserviceswidget.moc"
412
413#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 setColumnStretch(int column, int stretch)
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)
bool isEmpty() const const
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-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:12 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.