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"