File indexing completed on 2024-06-23 05:14:02

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     dialogs/certificateselectiondialog.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <config-kleopatra.h>
0011 
0012 #include "certificateselectiondialog.h"
0013 
0014 #include <kleopatraapplication.h>
0015 #include <settings.h>
0016 
0017 #include <view/keytreeview.h>
0018 #include <view/searchbar.h>
0019 #include <view/tabwidget.h>
0020 
0021 #include "utils/tags.h"
0022 
0023 #include <Libkleo/Algorithm>
0024 #include <Libkleo/Compat>
0025 #include <Libkleo/KeyCache>
0026 #include <Libkleo/KeyGroup>
0027 #include <Libkleo/KeyListModel>
0028 
0029 #include <commands/importcertificatefromfilecommand.h>
0030 #include <commands/lookupcertificatescommand.h>
0031 #include <commands/newopenpgpcertificatecommand.h>
0032 #include <commands/reloadkeyscommand.h>
0033 
0034 #include <gpgme++/key.h>
0035 
0036 #include <KConfigDialog>
0037 #include <KConfigGroup>
0038 #include <KLocalizedString>
0039 #include <KSharedConfig>
0040 
0041 #include <QAbstractItemView>
0042 #include <QDialogButtonBox>
0043 #include <QItemSelectionModel>
0044 #include <QLabel>
0045 #include <QPushButton>
0046 #include <QVBoxLayout>
0047 
0048 #include <algorithm>
0049 
0050 using namespace Kleo;
0051 using namespace Kleo::Dialogs;
0052 using namespace Kleo::Commands;
0053 using namespace GpgME;
0054 
0055 CertificateSelectionDialog::Option CertificateSelectionDialog::optionsFromProtocol(Protocol proto)
0056 {
0057     switch (proto) {
0058     case OpenPGP:
0059         return CertificateSelectionDialog::OpenPGPFormat;
0060     case CMS:
0061         return CertificateSelectionDialog::CMSFormat;
0062     default:
0063         return CertificateSelectionDialog::AnyFormat;
0064     }
0065 }
0066 
0067 namespace
0068 {
0069 auto protocolFromOptions(CertificateSelectionDialog::Options options)
0070 {
0071     switch (options & CertificateSelectionDialog::AnyFormat) {
0072     case CertificateSelectionDialog::OpenPGPFormat:
0073         return GpgME::OpenPGP;
0074     case CertificateSelectionDialog::CMSFormat:
0075         return GpgME::CMS;
0076     default:
0077         return GpgME::UnknownProtocol;
0078     }
0079 }
0080 }
0081 
0082 class CertificateSelectionDialog::Private
0083 {
0084     friend class ::Kleo::Dialogs::CertificateSelectionDialog;
0085     CertificateSelectionDialog *const q;
0086 
0087 public:
0088     explicit Private(CertificateSelectionDialog *qq);
0089 
0090 private:
0091     void reload()
0092     {
0093         Command *const cmd = new ReloadKeysCommand(nullptr);
0094         cmd->setParentWidget(q);
0095         cmd->start();
0096     }
0097     void create()
0098     {
0099         auto cmd = new NewOpenPGPCertificateCommand;
0100         cmd->setParentWidget(q);
0101         cmd->start();
0102     }
0103     void lookup()
0104     {
0105         const auto cmd = new LookupCertificatesCommand(nullptr);
0106         cmd->setParentWidget(q);
0107         cmd->setProtocol(protocolFromOptions(options));
0108         cmd->start();
0109     }
0110     void manageGroups()
0111     {
0112         // ensure that the dialog is shown on top of the modal CertificateSelectionDialog
0113         KleopatraApplication::instance()->openOrRaiseGroupsConfigDialog(q);
0114     }
0115     void slotKeysMayHaveChanged();
0116     void slotCurrentViewChanged(QAbstractItemView *newView);
0117     void slotSelectionChanged();
0118     void slotDoubleClicked(const QModelIndex &idx);
0119 
0120 private:
0121     bool acceptable(const std::vector<Key> &keys, const std::vector<KeyGroup> &groups)
0122     {
0123         return !keys.empty() || !groups.empty();
0124     }
0125     void updateLabelText()
0126     {
0127         ui.label.setText(!customLabelText.isEmpty()       ? customLabelText
0128                              : (options & MultiSelection) ? i18n("Please select one or more of the following certificates:")
0129                                                           : i18n("Please select one of the following certificates:"));
0130     }
0131 
0132 private:
0133     std::vector<QAbstractItemView *> connectedViews;
0134     QString customLabelText;
0135     Options options = AnyCertificate | AnyFormat;
0136 
0137     struct UI {
0138         QLabel label;
0139         SearchBar searchBar;
0140         TabWidget tabWidget;
0141         QDialogButtonBox buttonBox;
0142         QPushButton *createButton = nullptr;
0143     } ui;
0144 
0145     void setUpUI(CertificateSelectionDialog *q)
0146     {
0147         KDAB_SET_OBJECT_NAME(ui.label);
0148         KDAB_SET_OBJECT_NAME(ui.searchBar);
0149         KDAB_SET_OBJECT_NAME(ui.tabWidget);
0150         KDAB_SET_OBJECT_NAME(ui.buttonBox);
0151 
0152         auto vlay = new QVBoxLayout(q);
0153         vlay->addWidget(&ui.label);
0154         vlay->addWidget(&ui.searchBar);
0155         vlay->addWidget(&ui.tabWidget, 1);
0156         vlay->addWidget(&ui.buttonBox);
0157 
0158         QPushButton *const okButton = ui.buttonBox.addButton(QDialogButtonBox::Ok);
0159         okButton->setEnabled(false);
0160         ui.buttonBox.addButton(QDialogButtonBox::Close);
0161         QPushButton *const reloadButton = ui.buttonBox.addButton(i18n("Reload"), QDialogButtonBox::ActionRole);
0162         reloadButton->setToolTip(i18nc("@info:tooltip", "Refresh certificate list"));
0163         QPushButton *const importButton = ui.buttonBox.addButton(i18n("Import..."), QDialogButtonBox::ActionRole);
0164         importButton->setToolTip(i18nc("@info:tooltip", "Import certificate from file"));
0165         importButton->setAccessibleName(i18n("Import certificate"));
0166         QPushButton *const lookupButton = ui.buttonBox.addButton(i18n("Lookup..."), QDialogButtonBox::ActionRole);
0167         lookupButton->setToolTip(i18nc("@info:tooltip", "Look up certificate on server"));
0168         lookupButton->setAccessibleName(i18n("Look up certificate"));
0169         ui.createButton = ui.buttonBox.addButton(i18n("New..."), QDialogButtonBox::ActionRole);
0170         ui.createButton->setToolTip(i18nc("@info:tooltip", "Create a new OpenPGP certificate"));
0171         ui.createButton->setAccessibleName(i18n("Create certificate"));
0172         QPushButton *const groupsButton = ui.buttonBox.addButton(i18n("Groups..."), QDialogButtonBox::ActionRole);
0173         groupsButton->setToolTip(i18nc("@info:tooltip", "Manage certificate groups"));
0174         groupsButton->setAccessibleName(i18n("Manage groups"));
0175         groupsButton->setVisible(Settings().groupsEnabled());
0176 
0177         connect(&ui.buttonBox, &QDialogButtonBox::accepted, q, &CertificateSelectionDialog::accept);
0178         connect(&ui.buttonBox, &QDialogButtonBox::rejected, q, &CertificateSelectionDialog::reject);
0179         connect(reloadButton, &QPushButton::clicked, q, [this]() {
0180             reload();
0181         });
0182         connect(lookupButton, &QPushButton::clicked, q, [this]() {
0183             lookup();
0184         });
0185         connect(ui.createButton, &QPushButton::clicked, q, [this]() {
0186             create();
0187         });
0188         connect(groupsButton, &QPushButton::clicked, q, [this]() {
0189             manageGroups();
0190         });
0191         connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, q, [this]() {
0192             slotKeysMayHaveChanged();
0193         });
0194 
0195         connect(importButton, &QPushButton::clicked, q, [importButton, q]() {
0196             importButton->setEnabled(false);
0197             auto cmd = new Kleo::ImportCertificateFromFileCommand();
0198             connect(cmd, &Kleo::ImportCertificateFromFileCommand::finished, q, [importButton]() {
0199                 importButton->setEnabled(true);
0200             });
0201             cmd->setParentWidget(q);
0202             cmd->start();
0203         });
0204     }
0205 };
0206 
0207 CertificateSelectionDialog::Private::Private(CertificateSelectionDialog *qq)
0208     : q(qq)
0209 {
0210     setUpUI(q);
0211     ui.tabWidget.setFlatModel(AbstractKeyListModel::createFlatKeyListModel(q));
0212     ui.tabWidget.setHierarchicalModel(AbstractKeyListModel::createHierarchicalKeyListModel(q));
0213     const auto tagKeys = Tags::tagKeys();
0214     ui.tabWidget.flatModel()->setRemarkKeys(tagKeys);
0215     ui.tabWidget.hierarchicalModel()->setRemarkKeys(tagKeys);
0216     ui.tabWidget.connectSearchBar(&ui.searchBar);
0217 
0218     connect(&ui.tabWidget, &TabWidget::currentViewChanged, q, [this](QAbstractItemView *view) {
0219         slotCurrentViewChanged(view);
0220     });
0221 
0222     updateLabelText();
0223     q->setWindowTitle(i18nc("@title:window", "Certificate Selection"));
0224 }
0225 
0226 CertificateSelectionDialog::CertificateSelectionDialog(QWidget *parent)
0227     : QDialog(parent)
0228     , d(new Private(this))
0229 {
0230     const KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kleopatracertificateselectiondialogrc"));
0231     d->ui.tabWidget.loadViews(config.data());
0232     const KConfigGroup geometry(config, QStringLiteral("Geometry"));
0233     resize(geometry.readEntry("size", size()));
0234     d->slotKeysMayHaveChanged();
0235 }
0236 
0237 CertificateSelectionDialog::~CertificateSelectionDialog()
0238 {
0239 }
0240 
0241 void CertificateSelectionDialog::setCustomLabelText(const QString &txt)
0242 {
0243     if (txt == d->customLabelText) {
0244         return;
0245     }
0246     d->customLabelText = txt;
0247     d->updateLabelText();
0248 }
0249 
0250 QString CertificateSelectionDialog::customLabelText() const
0251 {
0252     return d->customLabelText;
0253 }
0254 
0255 void CertificateSelectionDialog::setOptions(Options options)
0256 {
0257     Q_ASSERT((options & CertificateSelectionDialog::AnyCertificate) != 0);
0258     Q_ASSERT((options & CertificateSelectionDialog::AnyFormat) != 0);
0259     if (d->options == options) {
0260         return;
0261     }
0262     d->options = options;
0263 
0264     d->ui.tabWidget.setMultiSelection(options & MultiSelection);
0265 
0266     d->slotKeysMayHaveChanged();
0267     d->updateLabelText();
0268     d->ui.createButton->setVisible(options & OpenPGPFormat);
0269 }
0270 
0271 CertificateSelectionDialog::Options CertificateSelectionDialog::options() const
0272 {
0273     return d->options;
0274 }
0275 
0276 void CertificateSelectionDialog::setStringFilter(const QString &filter)
0277 {
0278     d->ui.tabWidget.setStringFilter(filter);
0279 }
0280 
0281 void CertificateSelectionDialog::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
0282 {
0283     d->ui.tabWidget.setKeyFilter(filter);
0284 }
0285 
0286 namespace
0287 {
0288 
0289 void selectRows(const QAbstractItemView *view, const QModelIndexList &indexes)
0290 {
0291     if (!view) {
0292         return;
0293     }
0294     QItemSelectionModel *const sm = view->selectionModel();
0295     Q_ASSERT(sm);
0296 
0297     for (const QModelIndex &idx : std::as_const(indexes)) {
0298         if (idx.isValid()) {
0299             sm->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows);
0300         }
0301     }
0302 }
0303 
0304 QModelIndexList getGroupIndexes(const KeyListModelInterface *model, const std::vector<KeyGroup> &groups)
0305 {
0306     QModelIndexList indexes;
0307     indexes.reserve(groups.size());
0308     std::transform(groups.begin(), groups.end(), std::back_inserter(indexes), [model](const KeyGroup &group) {
0309         return model->index(group);
0310     });
0311     indexes.erase(std::remove_if(indexes.begin(),
0312                                  indexes.end(),
0313                                  [](const QModelIndex &index) {
0314                                      return !index.isValid();
0315                                  }),
0316                   indexes.end());
0317     return indexes;
0318 }
0319 
0320 }
0321 
0322 void CertificateSelectionDialog::selectCertificates(const std::vector<Key> &keys)
0323 {
0324     const auto *const model = d->ui.tabWidget.currentModel();
0325     Q_ASSERT(model);
0326     selectRows(d->ui.tabWidget.currentView(), model->indexes(keys));
0327 }
0328 
0329 void CertificateSelectionDialog::selectCertificate(const Key &key)
0330 {
0331     selectCertificates(std::vector<Key>(1, key));
0332 }
0333 
0334 void CertificateSelectionDialog::selectGroups(const std::vector<KeyGroup> &groups)
0335 {
0336     const auto *const model = d->ui.tabWidget.currentModel();
0337     Q_ASSERT(model);
0338     selectRows(d->ui.tabWidget.currentView(), getGroupIndexes(model, groups));
0339 }
0340 
0341 namespace
0342 {
0343 
0344 QModelIndexList getSelectedRows(const QAbstractItemView *view)
0345 {
0346     if (!view) {
0347         return {};
0348     }
0349     const QItemSelectionModel *const sm = view->selectionModel();
0350     Q_ASSERT(sm);
0351     return sm->selectedRows();
0352 }
0353 
0354 std::vector<KeyGroup> getGroups(const KeyListModelInterface *model, const QModelIndexList &indexes)
0355 {
0356     std::vector<KeyGroup> groups;
0357     groups.reserve(indexes.size());
0358     std::transform(indexes.begin(), indexes.end(), std::back_inserter(groups), [model](const QModelIndex &idx) {
0359         return model->group(idx);
0360     });
0361     groups.erase(std::remove_if(groups.begin(), groups.end(), std::mem_fn(&Kleo::KeyGroup::isNull)), groups.end());
0362     return groups;
0363 }
0364 
0365 }
0366 
0367 std::vector<Key> CertificateSelectionDialog::selectedCertificates() const
0368 {
0369     const KeyListModelInterface *const model = d->ui.tabWidget.currentModel();
0370     Q_ASSERT(model);
0371     return model->keys(getSelectedRows(d->ui.tabWidget.currentView()));
0372 }
0373 
0374 Key CertificateSelectionDialog::selectedCertificate() const
0375 {
0376     const std::vector<Key> keys = selectedCertificates();
0377     return keys.empty() ? Key() : keys.front();
0378 }
0379 
0380 std::vector<KeyGroup> CertificateSelectionDialog::selectedGroups() const
0381 {
0382     const KeyListModelInterface *const model = d->ui.tabWidget.currentModel();
0383     Q_ASSERT(model);
0384     return getGroups(model, getSelectedRows(d->ui.tabWidget.currentView()));
0385 }
0386 
0387 void CertificateSelectionDialog::hideEvent(QHideEvent *e)
0388 {
0389     KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kleopatracertificateselectiondialogrc"));
0390     d->ui.tabWidget.saveViews(config.data());
0391     KConfigGroup geometry(config, QStringLiteral("Geometry"));
0392     geometry.writeEntry("size", size());
0393     QDialog::hideEvent(e);
0394 }
0395 
0396 void CertificateSelectionDialog::Private::slotKeysMayHaveChanged()
0397 {
0398     q->setEnabled(true);
0399     std::vector<Key> keys = (options & SecretKeys) ? KeyCache::instance()->secretKeys() : KeyCache::instance()->keys();
0400     q->filterAllowedKeys(keys, options);
0401     const std::vector<KeyGroup> groups = (options & IncludeGroups) ? KeyCache::instance()->groups() : std::vector<KeyGroup>();
0402 
0403     const std::vector<Key> selectedKeys = q->selectedCertificates();
0404     const std::vector<KeyGroup> selectedGroups = q->selectedGroups();
0405     if (AbstractKeyListModel *const model = ui.tabWidget.flatModel()) {
0406         model->setKeys(keys);
0407         model->setGroups(groups);
0408     }
0409     if (AbstractKeyListModel *const model = ui.tabWidget.hierarchicalModel()) {
0410         model->setKeys(keys);
0411         model->setGroups(groups);
0412     }
0413     q->selectCertificates(selectedKeys);
0414     q->selectGroups(selectedGroups);
0415 }
0416 
0417 void CertificateSelectionDialog::filterAllowedKeys(std::vector<Key> &keys, int options)
0418 {
0419     auto end = keys.end();
0420 
0421     switch (options & AnyFormat) {
0422     case OpenPGPFormat:
0423         end = std::remove_if(keys.begin(), end, [](const Key &key) {
0424             return key.protocol() != OpenPGP;
0425         });
0426         break;
0427     case CMSFormat:
0428         end = std::remove_if(keys.begin(), end, [](const Key &key) {
0429             return key.protocol() != CMS;
0430         });
0431         break;
0432     default:
0433     case AnyFormat:;
0434     }
0435 
0436     switch (options & AnyCertificate) {
0437     case SignOnly:
0438         end = std::remove_if(keys.begin(), end, [](const Key &key) {
0439             return !Kleo::keyHasSign(key);
0440         });
0441         break;
0442     case EncryptOnly:
0443         end = std::remove_if(keys.begin(), end, [](const Key &key) {
0444             return !Kleo::keyHasEncrypt(key);
0445         });
0446         break;
0447     default:
0448     case AnyCertificate:;
0449     }
0450 
0451     if (options & SecretKeys) {
0452         end = std::remove_if(keys.begin(), end, [](const Key &key) {
0453             return !key.hasSecret();
0454         });
0455     }
0456 
0457     keys.erase(end, keys.end());
0458 }
0459 
0460 void CertificateSelectionDialog::Private::slotCurrentViewChanged(QAbstractItemView *newView)
0461 {
0462     if (!Kleo::contains(connectedViews, newView)) {
0463         connectedViews.push_back(newView);
0464         connect(newView, &QAbstractItemView::doubleClicked, q, [this](const QModelIndex &index) {
0465             slotDoubleClicked(index);
0466         });
0467         Q_ASSERT(newView->selectionModel());
0468         connect(newView->selectionModel(), &QItemSelectionModel::selectionChanged, q, [this](const QItemSelection &, const QItemSelection &) {
0469             slotSelectionChanged();
0470         });
0471     }
0472     slotSelectionChanged();
0473 }
0474 
0475 void CertificateSelectionDialog::Private::slotSelectionChanged()
0476 {
0477     if (QPushButton *const pb = ui.buttonBox.button(QDialogButtonBox::Ok)) {
0478         pb->setEnabled(acceptable(q->selectedCertificates(), q->selectedGroups()));
0479     }
0480 }
0481 
0482 void CertificateSelectionDialog::Private::slotDoubleClicked(const QModelIndex &idx)
0483 {
0484     QAbstractItemView *const view = ui.tabWidget.currentView();
0485     Q_ASSERT(view);
0486     const auto *const model = ui.tabWidget.currentModel();
0487     Q_ASSERT(model);
0488     Q_UNUSED(model)
0489     QItemSelectionModel *const sm = view->selectionModel();
0490     Q_ASSERT(sm);
0491     sm->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
0492     QMetaObject::invokeMethod(
0493         q,
0494         [this]() {
0495             q->accept();
0496         },
0497         Qt::QueuedConnection);
0498 }
0499 
0500 void CertificateSelectionDialog::accept()
0501 {
0502     if (d->acceptable(selectedCertificates(), selectedGroups())) {
0503         QDialog::accept();
0504     }
0505 }
0506 
0507 #include "moc_certificateselectiondialog.cpp"