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"