File indexing completed on 2025-01-05 04:55:47
0001 /* 0002 ui/directoryserviceswidget.cpp 0003 0004 This file is part of libkleopatra, the KDE keymanagement library 0005 SPDX-FileCopyrightText: 2001, 2002, 2004 Klarälvdalens Datakonsult AB 0006 SPDX-FileCopyrightText: 2017 Bundesamnt für Sicherheit in der Informationstechnik 0007 SPDX-FileCopyrightText: 2021 g10 Code GmbH 0008 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> 0009 0010 SPDX-License-Identifier: GPL-2.0-or-later 0011 */ 0012 0013 #include <config-libkleo.h> 0014 0015 #include "directoryserviceswidget.h" 0016 0017 #include "editdirectoryservicedialog.h" 0018 0019 #include <libkleo/gnupg.h> 0020 #include <libkleo/keyserverconfig.h> 0021 0022 #include <kleo_ui_debug.h> 0023 0024 #include <KLocalizedString> 0025 0026 #include <QListView> 0027 #include <QMenu> 0028 #include <QPointer> 0029 #include <QPushButton> 0030 #include <QToolButton> 0031 #include <QVBoxLayout> 0032 0033 using namespace Kleo; 0034 0035 namespace 0036 { 0037 0038 bool activeDirectoryIsSupported() 0039 { 0040 return engineIsVersion(2, 2, 28, GpgME::GpgSMEngine); 0041 } 0042 0043 bool isStandardActiveDirectory(const KeyserverConfig &keyserver) 0044 { 0045 return (keyserver.authentication() == KeyserverAuthentication::ActiveDirectory) && keyserver.host().isEmpty(); 0046 } 0047 0048 bool keyserverIsEditable(const KeyserverConfig &keyserver) 0049 { 0050 // standard AD is not editable 0051 return !isStandardActiveDirectory(keyserver); 0052 } 0053 0054 class KeyserverModel : public QAbstractListModel 0055 { 0056 Q_OBJECT 0057 public: 0058 explicit KeyserverModel(QObject *parent = nullptr) 0059 : QAbstractListModel{parent} 0060 { 0061 } 0062 0063 void setKeyservers(const std::vector<KeyserverConfig> &servers) 0064 { 0065 clear(); 0066 beginInsertRows(QModelIndex(), 0, servers.size() - 1); 0067 m_items = servers; 0068 endInsertRows(); 0069 } 0070 0071 void addKeyserver(const KeyserverConfig &keyserver) 0072 { 0073 const auto row = m_items.size(); 0074 beginInsertRows(QModelIndex(), row, row); 0075 m_items.push_back(keyserver); 0076 endInsertRows(); 0077 } 0078 0079 KeyserverConfig getKeyserver(unsigned int id) 0080 { 0081 if (id >= m_items.size()) { 0082 qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id; 0083 return {}; 0084 } 0085 0086 return m_items[id]; 0087 } 0088 0089 void updateKeyserver(unsigned int id, const KeyserverConfig &keyserver) 0090 { 0091 if (id >= m_items.size()) { 0092 qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id; 0093 return; 0094 } 0095 0096 m_items[id] = keyserver; 0097 Q_EMIT dataChanged(index(id), index(id)); 0098 } 0099 0100 void deleteKeyserver(unsigned int id) 0101 { 0102 if (id >= m_items.size()) { 0103 qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id; 0104 return; 0105 } 0106 0107 beginRemoveRows(QModelIndex(), id, id); 0108 m_items.erase(m_items.begin() + id); 0109 endRemoveRows(); 0110 } 0111 0112 void clear() 0113 { 0114 if (m_items.empty()) { 0115 return; 0116 } 0117 beginRemoveRows(QModelIndex(), 0, m_items.size() - 1); 0118 m_items.clear(); 0119 endRemoveRows(); 0120 } 0121 0122 int rowCount(const QModelIndex & = QModelIndex()) const override 0123 { 0124 return m_items.size(); 0125 } 0126 0127 QVariant data(const QModelIndex &index, int role) const override 0128 { 0129 if (!index.isValid()) { 0130 return {}; 0131 } 0132 switch (role) { 0133 case Qt::DisplayRole: 0134 case Qt::EditRole: { 0135 const auto keyserver = m_items[index.row()]; 0136 return isStandardActiveDirectory(keyserver) ? i18n("Active Directory") : keyserver.host(); 0137 } 0138 } 0139 return {}; 0140 } 0141 0142 bool hasActiveDirectory() 0143 { 0144 // check whether any of the model items represents an Active Directory keyserver 0145 return std::any_of(std::cbegin(m_items), std::cend(m_items), isStandardActiveDirectory); 0146 } 0147 0148 private: 0149 using QAbstractListModel::setData; 0150 0151 private: 0152 std::vector<KeyserverConfig> m_items; 0153 }; 0154 } 0155 0156 class DirectoryServicesWidget::Private 0157 { 0158 DirectoryServicesWidget *const q; 0159 0160 struct { 0161 QListView *keyserverList = nullptr; 0162 QToolButton *newButton = nullptr; 0163 QAction *addActiveDirectoryAction = nullptr; 0164 QAction *addLdapServerAction = nullptr; 0165 QPushButton *editButton = nullptr; 0166 QPushButton *deleteButton = nullptr; 0167 } ui; 0168 KeyserverModel *keyserverModel = nullptr; 0169 bool readOnly = false; 0170 0171 public: 0172 Private(DirectoryServicesWidget *qq) 0173 : q(qq) 0174 { 0175 auto mainLayout = new QVBoxLayout{q}; 0176 0177 auto gridLayout = new QGridLayout{}; 0178 gridLayout->setColumnStretch(0, 1); 0179 gridLayout->setRowStretch(1, 1); 0180 0181 keyserverModel = new KeyserverModel{q}; 0182 ui.keyserverList = new QListView(); 0183 ui.keyserverList->setModel(keyserverModel); 0184 ui.keyserverList->setModelColumn(0); 0185 ui.keyserverList->setSelectionBehavior(QAbstractItemView::SelectRows); 0186 ui.keyserverList->setSelectionMode(QAbstractItemView::SingleSelection); 0187 ui.keyserverList->setWhatsThis(i18nc("@info:whatsthis", "This is a list of all directory services that are configured for use with X.509.")); 0188 gridLayout->addWidget(ui.keyserverList, 1, 0); 0189 0190 auto groupsButtonLayout = new QVBoxLayout(); 0191 0192 auto menu = new QMenu{q}; 0193 ui.addActiveDirectoryAction = menu->addAction(i18n("Active Directory"), [this]() { 0194 addActiveDirectory(); 0195 }); 0196 ui.addActiveDirectoryAction->setToolTip(i18nc("@info:tooltip", 0197 "Click to use a directory service running on your Active Directory. " 0198 "This works only on Windows and requires GnuPG 2.2.28 or later.")); 0199 ui.addActiveDirectoryAction->setEnabled(activeDirectoryIsSupported()); 0200 ui.addLdapServerAction = menu->addAction(i18n("LDAP Server"), [this]() { 0201 addLdapServer(); 0202 }); 0203 ui.addLdapServerAction->setToolTip(i18nc("@info:tooltip", "Click to add a directory service provided by an LDAP server.")); 0204 ui.newButton = new QToolButton{q}; 0205 ui.newButton->setText(i18n("Add")); 0206 ui.newButton->setToolTip(i18nc("@info:tooltip", "Click to add a directory service.")); 0207 ui.newButton->setWhatsThis(i18nc("@info:whatsthis", 0208 "Click this button to add a directory service to the list of services. " 0209 "The change will only take effect once you acknowledge the configuration dialog.")); 0210 ui.newButton->setToolButtonStyle(Qt::ToolButtonTextOnly); 0211 ui.newButton->setPopupMode(QToolButton::InstantPopup); 0212 ui.newButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); // expand horizontally like the QPushButtons 0213 ui.newButton->setMenu(menu); 0214 groupsButtonLayout->addWidget(ui.newButton); 0215 0216 ui.editButton = new QPushButton(i18n("Edit")); 0217 ui.editButton->setToolTip(i18nc("@info:tooltip", "Click to edit the selected service.")); 0218 ui.editButton->setWhatsThis(i18nc("@info:whatsthis", 0219 "Click this button to edit the settings of the currently selected directory service. " 0220 "The changes will only take effect once you acknowledge the configuration dialog.")); 0221 ui.editButton->setEnabled(false); 0222 groupsButtonLayout->addWidget(ui.editButton); 0223 0224 ui.deleteButton = new QPushButton(i18n("Delete")); 0225 ui.deleteButton->setToolTip(i18nc("@info:tooltip", "Click to remove the selected service.")); 0226 ui.deleteButton->setWhatsThis(i18nc("@info:whatsthis", 0227 "Click this button to remove the currently selected directory service. " 0228 "The change will only take effect once you acknowledge the configuration dialog.")); 0229 ui.deleteButton->setEnabled(false); 0230 groupsButtonLayout->addWidget(ui.deleteButton); 0231 0232 groupsButtonLayout->addStretch(1); 0233 0234 gridLayout->addLayout(groupsButtonLayout, 1, 1); 0235 0236 mainLayout->addLayout(gridLayout, /*stretch=*/1); 0237 0238 connect(keyserverModel, &QAbstractItemModel::dataChanged, q, [this]() { 0239 modelChanged(); 0240 }); 0241 connect(keyserverModel, &QAbstractItemModel::rowsInserted, q, [this]() { 0242 modelChanged(); 0243 }); 0244 connect(keyserverModel, &QAbstractItemModel::rowsRemoved, q, [this]() { 0245 modelChanged(); 0246 }); 0247 connect(ui.keyserverList->selectionModel(), &QItemSelectionModel::selectionChanged, q, [this]() { 0248 selectionChanged(); 0249 }); 0250 connect(ui.keyserverList, &QListView::doubleClicked, q, [this](const QModelIndex &index) { 0251 if (!readOnly) { 0252 editKeyserver(index); 0253 } 0254 }); 0255 connect(ui.editButton, &QPushButton::clicked, q, [this]() { 0256 editKeyserver(); 0257 }); 0258 connect(ui.deleteButton, &QPushButton::clicked, q, [this]() { 0259 deleteKeyserver(); 0260 }); 0261 } 0262 0263 void setReadOnly(bool ro) 0264 { 0265 readOnly = ro; 0266 updateActions(); 0267 } 0268 0269 void setKeyservers(const std::vector<KeyserverConfig> &servers) 0270 { 0271 keyserverModel->setKeyservers(servers); 0272 } 0273 0274 std::vector<KeyserverConfig> keyservers() const 0275 { 0276 std::vector<KeyserverConfig> result; 0277 result.reserve(keyserverModel->rowCount()); 0278 for (int row = 0; row < keyserverModel->rowCount(); ++row) { 0279 result.push_back(keyserverModel->getKeyserver(row)); 0280 } 0281 return result; 0282 } 0283 0284 void clear() 0285 { 0286 if (keyserverModel->rowCount() == 0) { 0287 return; 0288 } 0289 keyserverModel->clear(); 0290 } 0291 0292 private: 0293 auto selectedIndex() 0294 { 0295 const auto indexes = ui.keyserverList->selectionModel()->selectedRows(); 0296 return indexes.empty() ? QModelIndex() : indexes[0]; 0297 } 0298 0299 void modelChanged() 0300 { 0301 updateActions(); 0302 Q_EMIT q->changed(); 0303 } 0304 0305 void selectionChanged() 0306 { 0307 updateActions(); 0308 } 0309 0310 void updateActions() 0311 { 0312 const auto index = selectedIndex(); 0313 ui.newButton->setEnabled(!readOnly); 0314 ui.addActiveDirectoryAction->setEnabled(activeDirectoryIsSupported() && !keyserverModel->hasActiveDirectory()); 0315 ui.editButton->setEnabled(!readOnly && index.isValid() && keyserverIsEditable(keyserverModel->getKeyserver(index.row()))); 0316 ui.deleteButton->setEnabled(!readOnly && index.isValid()); 0317 } 0318 0319 void handleEditKeyserverDialogResult(const int id, const EditDirectoryServiceDialog *dialog) 0320 { 0321 if (id >= 0) { 0322 keyserverModel->updateKeyserver(id, dialog->keyserver()); 0323 } else { 0324 keyserverModel->addKeyserver(dialog->keyserver()); 0325 } 0326 } 0327 0328 void showEditKeyserverDialog(const int id, const KeyserverConfig &keyserver, const QString &windowTitle) 0329 { 0330 QPointer<EditDirectoryServiceDialog> dialog{new EditDirectoryServiceDialog{q}}; 0331 dialog->setAttribute(Qt::WA_DeleteOnClose); 0332 dialog->setWindowModality(Qt::WindowModal); 0333 dialog->setWindowTitle(windowTitle); 0334 dialog->setKeyserver(keyserver); 0335 0336 connect(dialog, &QDialog::accepted, q, [dialog, id, this] { 0337 handleEditKeyserverDialogResult(id, dialog); 0338 }); 0339 0340 dialog->show(); 0341 } 0342 0343 void addActiveDirectory() 0344 { 0345 KeyserverConfig keyserver; 0346 keyserver.setAuthentication(KeyserverAuthentication::ActiveDirectory); 0347 keyserverModel->addKeyserver(keyserver); 0348 } 0349 0350 void addLdapServer() 0351 { 0352 showEditKeyserverDialog(-1, {}, i18nc("@title:window", "LDAP Directory Service")); 0353 } 0354 0355 void editKeyserver(const QModelIndex &index = {}) 0356 { 0357 const auto serverIndex = index.isValid() ? index : selectedIndex(); 0358 if (!serverIndex.isValid()) { 0359 qCDebug(KLEO_UI_LOG) << __func__ << "selection is empty"; 0360 return; 0361 } 0362 const auto id = serverIndex.row(); 0363 const KeyserverConfig keyserver = keyserverModel->getKeyserver(id); 0364 if (!keyserverIsEditable(keyserver)) { 0365 qCDebug(KLEO_UI_LOG) << __func__ << "selected keyserver (id:" << id << ") cannot be modified"; 0366 return; 0367 } 0368 0369 showEditKeyserverDialog(id, keyserver, i18nc("@title:window", "LDAP Directory Service")); 0370 } 0371 0372 void deleteKeyserver() 0373 { 0374 const QModelIndex serverIndex = selectedIndex(); 0375 if (!serverIndex.isValid()) { 0376 qCDebug(KLEO_UI_LOG) << __func__ << "selection is empty"; 0377 return; 0378 } 0379 keyserverModel->deleteKeyserver(serverIndex.row()); 0380 } 0381 }; 0382 0383 DirectoryServicesWidget::DirectoryServicesWidget(QWidget *parent) 0384 : QWidget{parent} 0385 , d{std::make_unique<Private>(this)} 0386 { 0387 } 0388 0389 DirectoryServicesWidget::~DirectoryServicesWidget() = default; 0390 0391 void DirectoryServicesWidget::setKeyservers(const std::vector<KeyserverConfig> &servers) 0392 { 0393 d->setKeyservers(servers); 0394 } 0395 0396 std::vector<KeyserverConfig> DirectoryServicesWidget::keyservers() const 0397 { 0398 return d->keyservers(); 0399 } 0400 0401 void DirectoryServicesWidget::setReadOnly(bool readOnly) 0402 { 0403 d->setReadOnly(readOnly); 0404 } 0405 0406 void DirectoryServicesWidget::clear() 0407 { 0408 d->clear(); 0409 } 0410 0411 #include "directoryserviceswidget.moc" 0412 0413 #include "moc_directoryserviceswidget.cpp"