Libkleo

keyselectiondialog.cpp
1 /* -*- c++ -*-
2  keyselectiondialog.cpp
3 
4  This file is part of libkleopatra, the KDE keymanagement library
5  SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
6 
7  Based on kpgpui.cpp
8  SPDX-FileCopyrightText: 2001, 2002 the KPGP authors
9  See file libkdenetwork/AUTHORS.kpgp for details
10 
11  SPDX-License-Identifier: GPL-2.0-or-later
12 */
13 
14 #include <config-libkleo.h>
15 
16 #include "keyselectiondialog.h"
17 
18 #include "keylistview.h"
19 #include "progressdialog.h"
20 
21 #include <libkleo/compat.h>
22 #include <libkleo/compliance.h>
23 #include <libkleo/dn.h>
24 #include <libkleo/formatting.h>
25 
26 #include <kleo_ui_debug.h>
27 
28 #include <KConfig>
29 #include <KConfigGroup>
30 #include <KLocalizedString>
31 #include <KMessageBox>
32 #include <KSharedConfig>
33 
34 #include <QGpgME/KeyListJob>
35 
36 #include <QApplication>
37 #include <QCheckBox>
38 #include <QDialogButtonBox>
39 #include <QFrame>
40 #include <QHBoxLayout>
41 #include <QLabel>
42 #include <QLineEdit>
43 #include <QMenu>
44 #include <QProcess>
45 #include <QPushButton>
46 #include <QRegularExpression>
47 #include <QScrollBar>
48 #include <QTimer>
49 #include <QVBoxLayout>
50 
51 #include <gpgme++/key.h>
52 #include <gpgme++/keylistresult.h>
53 
54 #include <algorithm>
55 #include <iterator>
56 #include <string.h>
57 
58 using namespace Kleo;
59 
60 static bool checkKeyUsage(const GpgME::Key &key, unsigned int keyUsage, QString *statusString = nullptr)
61 {
62  auto setStatusString = [statusString](const QString &status) {
63  if (statusString) {
64  *statusString = status;
65  }
66  };
67 
68  if (keyUsage & KeySelectionDialog::ValidKeys) {
69  if (key.isInvalid()) {
70  if (key.keyListMode() & GpgME::Validate) {
71  qCDebug(KLEO_UI_LOG) << "key is invalid";
72  setStatusString(i18n("The key is not valid."));
73  return false;
74  } else {
75  qCDebug(KLEO_UI_LOG) << "key is invalid - ignoring";
76  }
77  }
78  if (key.isExpired()) {
79  qCDebug(KLEO_UI_LOG) << "key is expired";
80  setStatusString(i18n("The key is expired."));
81  return false;
82  } else if (key.isRevoked()) {
83  qCDebug(KLEO_UI_LOG) << "key is revoked";
84  setStatusString(i18n("The key is revoked."));
85  return false;
86  } else if (key.isDisabled()) {
87  qCDebug(KLEO_UI_LOG) << "key is disabled";
88  setStatusString(i18n("The key is disabled."));
89  return false;
90  }
91  }
92 
93  if (keyUsage & KeySelectionDialog::EncryptionKeys && !Kleo::keyHasEncrypt(key)) {
94  qCDebug(KLEO_UI_LOG) << "key can't encrypt";
95  setStatusString(i18n("The key is not designated for encryption."));
96  return false;
97  }
98  if (keyUsage & KeySelectionDialog::SigningKeys && !Kleo::keyHasSign(key)) {
99  qCDebug(KLEO_UI_LOG) << "key can't sign";
100  setStatusString(i18n("The key is not designated for signing."));
101  return false;
102  }
103  if (keyUsage & KeySelectionDialog::CertificationKeys && !Kleo::keyHasCertify(key)) {
104  qCDebug(KLEO_UI_LOG) << "key can't certify";
105  setStatusString(i18n("The key is not designated for certifying."));
106  return false;
107  }
108  if (keyUsage & KeySelectionDialog::AuthenticationKeys && !Kleo::keyHasAuthenticate(key)) {
109  qCDebug(KLEO_UI_LOG) << "key can't authenticate";
110  setStatusString(i18n("The key is not designated for authentication."));
111  return false;
112  }
113 
114  if (keyUsage & KeySelectionDialog::SecretKeys && !(keyUsage & KeySelectionDialog::PublicKeys) && !key.hasSecret()) {
115  qCDebug(KLEO_UI_LOG) << "key isn't secret";
116  setStatusString(i18n("The key is not secret."));
117  return false;
118  }
119 
120  if (keyUsage & KeySelectionDialog::TrustedKeys && key.protocol() == GpgME::OpenPGP &&
121  // only check this for secret keys for now.
122  // Seems validity isn't checked for secret keylistings...
123  !key.hasSecret()) {
124  std::vector<GpgME::UserID> uids = key.userIDs();
125  for (std::vector<GpgME::UserID>::const_iterator it = uids.begin(); it != uids.end(); ++it) {
126  if (!it->isRevoked() && it->validity() >= GpgME::UserID::Marginal) {
127  setStatusString(i18n("The key can be used."));
128  return true;
129  }
130  }
131  qCDebug(KLEO_UI_LOG) << "key has no UIDs with validity >= Marginal";
132  setStatusString(i18n("The key is not trusted enough."));
133  return false;
134  }
135  // X.509 keys are always trusted, else they won't be the keybox.
136  // PENDING(marc) check that this ^ is correct
137 
138  setStatusString(i18n("The key can be used."));
139  return true;
140 }
141 
142 static bool checkKeyUsage(const std::vector<GpgME::Key> &keys, unsigned int keyUsage)
143 {
144  for (auto it = keys.begin(); it != keys.end(); ++it) {
145  if (!checkKeyUsage(*it, keyUsage)) {
146  return false;
147  }
148  }
149  return true;
150 }
151 
152 namespace
153 {
154 
155 class ColumnStrategy : public KeyListView::ColumnStrategy
156 {
157 public:
158  ColumnStrategy(unsigned int keyUsage);
159 
160  QString title(int col) const override;
161  int width(int col, const QFontMetrics &fm) const override;
162 
163  QString text(const GpgME::Key &key, int col) const override;
164  QString accessibleText(const GpgME::Key &key, int column) const override;
165  QString toolTip(const GpgME::Key &key, int col) const override;
166  QIcon icon(const GpgME::Key &key, int col) const override;
167 
168 private:
169  const QIcon mKeyGoodPix, mKeyBadPix, mKeyUnknownPix, mKeyValidPix;
170  const unsigned int mKeyUsage;
171 };
172 
173 static QString iconPath(const QString &name)
174 {
175  return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("libkleopatra/pics/") + name + QStringLiteral(".png"));
176 }
177 
178 ColumnStrategy::ColumnStrategy(unsigned int keyUsage)
179  : KeyListView::ColumnStrategy()
180  , mKeyGoodPix(iconPath(QStringLiteral("key_ok")))
181  , mKeyBadPix(iconPath(QStringLiteral("key_bad")))
182  , mKeyUnknownPix(iconPath(QStringLiteral("key_unknown")))
183  , mKeyValidPix(iconPath(QStringLiteral("key")))
184  , mKeyUsage(keyUsage)
185 {
186  if (keyUsage == 0) {
187  qCWarning(KLEO_UI_LOG) << "KeySelectionDialog: keyUsage == 0. You want to use AllKeys instead.";
188  }
189 }
190 
191 QString ColumnStrategy::title(int col) const
192 {
193  switch (col) {
194  case 0:
195  return i18n("Key ID");
196  case 1:
197  return i18n("User ID");
198  default:
199  return QString();
200  }
201 }
202 
203 int ColumnStrategy::width(int col, const QFontMetrics &fm) const
204 {
205  if (col == 0) {
206  static const char hexchars[] = "0123456789ABCDEF";
207  int maxWidth = 0;
208  for (unsigned int i = 0; i < 16; ++i) {
209  maxWidth = qMax(fm.boundingRect(QLatin1Char(hexchars[i])).width(), maxWidth);
210  }
211  return 8 * maxWidth + 2 * 16 /* KIconLoader::SizeSmall */;
212  }
213  return KeyListView::ColumnStrategy::width(col, fm);
214 }
215 
216 QString ColumnStrategy::text(const GpgME::Key &key, int col) const
217 {
218  switch (col) {
219  case 0: {
220  if (key.shortKeyID()) {
221  return Formatting::prettyID(key.shortKeyID());
222  } else {
223  return xi18n("<placeholder>unknown</placeholder>");
224  }
225  }
226  case 1: {
227  const char *uid = key.userID(0).id();
228  if (key.protocol() == GpgME::OpenPGP) {
229  return uid && *uid ? QString::fromUtf8(uid) : QString();
230  } else { // CMS
231  return DN(uid).prettyDN();
232  }
233  }
234  default:
235  return QString();
236  }
237 }
238 
239 QString ColumnStrategy::accessibleText(const GpgME::Key &key, int col) const
240 {
241  switch (col) {
242  case 0: {
243  if (key.shortKeyID()) {
244  return Formatting::accessibleHexID(key.shortKeyID());
245  }
246  [[fallthrough]];
247  }
248  default:
249  return {};
250  }
251 }
252 
253 QString ColumnStrategy::toolTip(const GpgME::Key &key, int) const
254 {
255  const char *uid = key.userID(0).id();
256  const char *fpr = key.primaryFingerprint();
257  const char *issuer = key.issuerName();
258  const GpgME::Subkey subkey = key.subkey(0);
259  const QString expiry = Formatting::expirationDateString(subkey);
260  const QString creation = Formatting::creationDateString(subkey);
261  QString keyStatusString;
262  if (!checkKeyUsage(key, mKeyUsage, &keyStatusString)) {
263  // Show the status in bold if there is a problem
264  keyStatusString = QLatin1StringView("<b>") % keyStatusString % QLatin1String("</b>");
265  }
266 
267  QString html = QStringLiteral("<qt><p style=\"style='white-space:pre'\">");
268  if (key.protocol() == GpgME::OpenPGP) {
269  html += i18n("OpenPGP key for <b>%1</b>", uid ? QString::fromUtf8(uid) : i18n("unknown"));
270  } else {
271  html += i18n("S/MIME key for <b>%1</b>", uid ? DN(uid).prettyDN() : i18n("unknown"));
272  }
273  html += QStringLiteral("</p><table>");
274 
275  const auto addRow = [&html](const QString &name, const QString &value) {
276  html += QStringLiteral("<tr><td align=\"right\"><b>%1: </b></td><td>%2</td></tr>").arg(name, value);
277  };
278  addRow(i18n("Valid from"), creation);
279  addRow(i18n("Valid until"), expiry);
280  addRow(i18nc("Key fingerprint", "Fingerprint"), fpr ? QString::fromLatin1(fpr) : i18n("unknown"));
281  if (key.protocol() != GpgME::OpenPGP) {
282  addRow(i18nc("Key issuer", "Issuer"), issuer ? DN(issuer).prettyDN() : i18n("unknown"));
283  }
284  addRow(i18nc("Key status", "Status"), keyStatusString);
285  if (DeVSCompliance::isActive()) {
286  addRow(i18nc("Compliance of key", "Compliance"), DeVSCompliance::name(key.isDeVs()));
287  }
288  html += QStringLiteral("</table></qt>");
289 
290  return html;
291 }
292 
293 QIcon ColumnStrategy::icon(const GpgME::Key &key, int col) const
294 {
295  if (col != 0) {
296  return QIcon();
297  }
298  // this key did not undergo a validating keylisting yet:
299  if (!(key.keyListMode() & GpgME::Validate)) {
300  return mKeyUnknownPix;
301  }
302 
303  if (!checkKeyUsage(key, mKeyUsage)) {
304  return mKeyBadPix;
305  }
306 
307  if (key.protocol() == GpgME::CMS) {
308  return mKeyGoodPix;
309  }
310 
311  switch (key.userID(0).validity()) {
312  default:
313  case GpgME::UserID::Unknown:
314  case GpgME::UserID::Undefined:
315  return mKeyUnknownPix;
316  case GpgME::UserID::Never:
317  return mKeyValidPix;
318  case GpgME::UserID::Marginal:
319  case GpgME::UserID::Full:
320  case GpgME::UserID::Ultimate: {
321  if (DeVSCompliance::isActive() && !key.isDeVs()) {
322  return mKeyValidPix;
323  }
324  return mKeyGoodPix;
325  }
326  }
327 }
328 
329 }
330 
331 static const int sCheckSelectionDelay = 250;
332 
333 KeySelectionDialog::KeySelectionDialog(QWidget *parent, Options options)
334  : QDialog(parent)
335  , mOpenPGPBackend(QGpgME::openpgp())
336  , mSMIMEBackend(QGpgME::smime())
337  , mKeyUsage(AllKeys)
338 {
339  qCDebug(KLEO_UI_LOG) << "mTruncated:" << mTruncated << "mSavedOffsetY:" << mSavedOffsetY;
340  setUpUI(options, QString());
341 }
342 
343 KeySelectionDialog::KeySelectionDialog(const QString &title,
344  const QString &text,
345  const std::vector<GpgME::Key> &selectedKeys,
346  unsigned int keyUsage,
347  bool extendedSelection,
348  bool rememberChoice,
349  QWidget *parent,
350  bool modal)
351  : QDialog(parent)
352  , mSelectedKeys(selectedKeys)
353  , mKeyUsage(keyUsage)
354 {
355  setWindowTitle(title);
356  setModal(modal);
357  init(rememberChoice, extendedSelection, text, QString());
358 }
359 
360 KeySelectionDialog::KeySelectionDialog(const QString &title,
361  const QString &text,
362  const QString &initialQuery,
363  const std::vector<GpgME::Key> &selectedKeys,
364  unsigned int keyUsage,
365  bool extendedSelection,
366  bool rememberChoice,
367  QWidget *parent,
368  bool modal)
369  : QDialog(parent)
370  , mSelectedKeys(selectedKeys)
371  , mKeyUsage(keyUsage)
372  , mSearchText(initialQuery)
373  , mInitialQuery(initialQuery)
374 {
375  setWindowTitle(title);
376  setModal(modal);
377  init(rememberChoice, extendedSelection, text, initialQuery);
378 }
379 
380 KeySelectionDialog::KeySelectionDialog(const QString &title,
381  const QString &text,
382  const QString &initialQuery,
383  unsigned int keyUsage,
384  bool extendedSelection,
385  bool rememberChoice,
386  QWidget *parent,
387  bool modal)
388  : QDialog(parent)
389  , mKeyUsage(keyUsage)
390  , mSearchText(initialQuery)
391  , mInitialQuery(initialQuery)
392 {
393  setWindowTitle(title);
394  setModal(modal);
395  init(rememberChoice, extendedSelection, text, initialQuery);
396 }
397 
398 void KeySelectionDialog::setUpUI(Options options, const QString &initialQuery)
399 {
400  auto mainLayout = new QVBoxLayout(this);
402  mOkButton = buttonBox->button(QDialogButtonBox::Ok);
403  mOkButton->setDefault(true);
404  mOkButton->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Return));
405 
406  mCheckSelectionTimer = new QTimer(this);
407  mStartSearchTimer = new QTimer(this);
408 
409  QFrame *page = new QFrame(this);
410  mainLayout->addWidget(page);
411  mainLayout->addWidget(buttonBox);
412 
413  mTopLayout = new QVBoxLayout(page);
414  mTopLayout->setContentsMargins(0, 0, 0, 0);
415 
416  mTextLabel = new QLabel(page);
417  mTextLabel->setWordWrap(true);
418 
419  // Setting the size policy is necessary as a workaround for https://issues.kolab.org/issue4429
420  // and http://bugreports.qt.nokia.com/browse/QTBUG-8740
422  connect(mTextLabel, &QLabel::linkActivated, this, &KeySelectionDialog::slotStartCertificateManager);
423  mTopLayout->addWidget(mTextLabel);
424  mTextLabel->hide();
425 
426  QPushButton *const searchExternalPB = new QPushButton(i18n("Search for &External Certificates"), page);
427  mTopLayout->addWidget(searchExternalPB, 0, Qt::AlignLeft);
428  connect(searchExternalPB, &QAbstractButton::clicked, this, &KeySelectionDialog::slotStartSearchForExternalCertificates);
429  if (initialQuery.isEmpty()) {
430  searchExternalPB->hide();
431  }
432 
433  auto hlay = new QHBoxLayout();
434  mTopLayout->addLayout(hlay);
435 
436  auto le = new QLineEdit(page);
437  le->setClearButtonEnabled(true);
438  le->setText(initialQuery);
439 
440  QLabel *lbSearchFor = new QLabel(i18n("&Search for:"), page);
441  lbSearchFor->setBuddy(le);
442 
443  hlay->addWidget(lbSearchFor);
444  hlay->addWidget(le, 1);
445  le->setFocus();
446 
447  connect(le, &QLineEdit::textChanged, this, [this](const QString &s) {
448  slotSearch(s);
449  });
450  connect(mStartSearchTimer, &QTimer::timeout, this, &KeySelectionDialog::slotFilter);
451 
452  mKeyListView = new KeyListView(new ColumnStrategy(mKeyUsage), nullptr, page);
453  mKeyListView->setObjectName(QLatin1StringView("mKeyListView"));
454  mKeyListView->header()->stretchLastSection();
455  mKeyListView->setRootIsDecorated(true);
456  mKeyListView->setSortingEnabled(true);
457  mKeyListView->header()->setSortIndicatorShown(true);
458  mKeyListView->header()->setSortIndicator(1, Qt::AscendingOrder); // sort by User ID
459  if (options & ExtendedSelection) {
460  mKeyListView->setSelectionMode(QAbstractItemView::ExtendedSelection);
461  }
462  mTopLayout->addWidget(mKeyListView, 10);
463 
464  if (options & RememberChoice) {
465  mRememberCB = new QCheckBox(i18n("&Remember choice"), page);
466  mTopLayout->addWidget(mRememberCB);
467  mRememberCB->setWhatsThis(
468  i18n("<qt><p>If you check this box your choice will "
469  "be stored and you will not be asked again."
470  "</p></qt>"));
471  }
472 
473  connect(mCheckSelectionTimer, &QTimer::timeout, this, [this]() {
474  slotCheckSelection();
475  });
476  connectSignals();
477 
478  connect(mKeyListView, &KeyListView::doubleClicked, this, &KeySelectionDialog::slotTryOk);
479  connect(mKeyListView, &KeyListView::contextMenu, this, &KeySelectionDialog::slotRMB);
480 
481  if (options & RereadKeys) {
482  QPushButton *button = new QPushButton(i18n("&Reread Keys"));
483  buttonBox->addButton(button, QDialogButtonBox::ActionRole);
484  connect(button, &QPushButton::clicked, this, &KeySelectionDialog::slotRereadKeys);
485  }
486  if (options & ExternalCertificateManager) {
487  QPushButton *button = new QPushButton(i18n("&Start Certificate Manager"));
488  buttonBox->addButton(button, QDialogButtonBox::ActionRole);
489  connect(button, &QPushButton::clicked, this, [this]() {
490  slotStartCertificateManager();
491  });
492  }
493  connect(mOkButton, &QPushButton::clicked, this, &KeySelectionDialog::slotOk);
494  connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &KeySelectionDialog::slotCancel);
495 
496  mTopLayout->activate();
497 
498  if (qApp) {
499  QSize dialogSize(sizeHint());
500  KConfigGroup dialogConfig(KSharedConfig::openStateConfig(), QStringLiteral("Key Selection Dialog"));
501  dialogSize = dialogConfig.readEntry("Dialog size", dialogSize);
502  const QByteArray headerState = dialogConfig.readEntry("header", QByteArray());
503  if (!headerState.isEmpty()) {
504  mKeyListView->header()->restoreState(headerState);
505  }
506  resize(dialogSize);
507  }
508 }
509 
510 void KeySelectionDialog::init(bool rememberChoice, bool extendedSelection, const QString &text, const QString &initialQuery)
511 {
512  Options options = {RereadKeys, ExternalCertificateManager};
513  options.setFlag(ExtendedSelection, extendedSelection);
514  options.setFlag(RememberChoice, rememberChoice);
515 
516  setUpUI(options, initialQuery);
517  setText(text);
518 
519  if (mKeyUsage & OpenPGPKeys) {
520  mOpenPGPBackend = QGpgME::openpgp();
521  }
522  if (mKeyUsage & SMIMEKeys) {
523  mSMIMEBackend = QGpgME::smime();
524  }
525 
526  slotRereadKeys();
527 }
528 
529 KeySelectionDialog::~KeySelectionDialog()
530 {
531  disconnectSignals();
532  KConfigGroup dialogConfig(KSharedConfig::openStateConfig(), QStringLiteral("Key Selection Dialog"));
533  dialogConfig.writeEntry("Dialog size", size());
534  dialogConfig.writeEntry("header", mKeyListView->header()->saveState());
535  dialogConfig.sync();
536 }
537 
538 void KeySelectionDialog::setText(const QString &text)
539 {
540  mTextLabel->setText(text);
541  mTextLabel->setVisible(!text.isEmpty());
542 }
543 
544 void KeySelectionDialog::setKeys(const std::vector<GpgME::Key> &keys)
545 {
546  for (const GpgME::Key &key : keys) {
547  mKeyListView->slotAddKey(key);
548  }
549 }
550 
551 void KeySelectionDialog::connectSignals()
552 {
553  if (mKeyListView->isMultiSelection()) {
554  connect(mKeyListView, &QTreeWidget::itemSelectionChanged, this, &KeySelectionDialog::slotSelectionChanged);
555  } else {
556  connect(mKeyListView,
557  qOverload<KeyListViewItem *>(&KeyListView::selectionChanged),
558  this,
559  qOverload<KeyListViewItem *>(&KeySelectionDialog::slotCheckSelection));
560  }
561 }
562 
563 void KeySelectionDialog::disconnectSignals()
564 {
565  if (mKeyListView->isMultiSelection()) {
566  disconnect(mKeyListView, &QTreeWidget::itemSelectionChanged, this, &KeySelectionDialog::slotSelectionChanged);
567  } else {
568  disconnect(mKeyListView,
569  qOverload<KeyListViewItem *>(&KeyListView::selectionChanged),
570  this,
571  qOverload<KeyListViewItem *>(&KeySelectionDialog::slotCheckSelection));
572  }
573 }
574 
575 const GpgME::Key &KeySelectionDialog::selectedKey() const
576 {
577  static const GpgME::Key null = GpgME::Key::null;
578  if (mKeyListView->isMultiSelection() || !mKeyListView->selectedItem()) {
579  return null;
580  }
581  return mKeyListView->selectedItem()->key();
582 }
583 
584 QString KeySelectionDialog::fingerprint() const
585 {
586  return QLatin1StringView(selectedKey().primaryFingerprint());
587 }
588 
589 QStringList KeySelectionDialog::fingerprints() const
590 {
591  QStringList result;
592  for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) {
593  if (const char *fpr = it->primaryFingerprint()) {
594  result.push_back(QLatin1StringView(fpr));
595  }
596  }
597  return result;
598 }
599 
600 QStringList KeySelectionDialog::pgpKeyFingerprints() const
601 {
602  QStringList result;
603  for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) {
604  if (it->protocol() == GpgME::OpenPGP) {
605  if (const char *fpr = it->primaryFingerprint()) {
606  result.push_back(QLatin1StringView(fpr));
607  }
608  }
609  }
610  return result;
611 }
612 
613 QStringList KeySelectionDialog::smimeFingerprints() const
614 {
615  QStringList result;
616  for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) {
617  if (it->protocol() == GpgME::CMS) {
618  if (const char *fpr = it->primaryFingerprint()) {
619  result.push_back(QLatin1StringView(fpr));
620  }
621  }
622  }
623  return result;
624 }
625 
626 void KeySelectionDialog::slotRereadKeys()
627 {
628  mKeyListView->clear();
629  mListJobCount = 0;
630  mTruncated = 0;
631  mSavedOffsetY = mKeyListView->verticalScrollBar()->value();
632 
633  disconnectSignals();
634  mKeyListView->setEnabled(false);
635 
636  // FIXME: save current selection
637  if (mOpenPGPBackend) {
638  startKeyListJobForBackend(mOpenPGPBackend, std::vector<GpgME::Key>(), false /*non-validating*/);
639  }
640  if (mSMIMEBackend) {
641  startKeyListJobForBackend(mSMIMEBackend, std::vector<GpgME::Key>(), false /*non-validating*/);
642  }
643 
644  if (mListJobCount == 0) {
645  mKeyListView->setEnabled(true);
647  i18n("No backends found for listing keys. "
648  "Check your installation."),
649  i18n("Key Listing Failed"));
650  connectSignals();
651  }
652 }
653 
654 void KeySelectionDialog::slotStartCertificateManager(const QString &query)
655 {
656  QStringList args;
657 
658  if (!query.isEmpty()) {
659  args << QStringLiteral("--search") << query;
660  }
661  const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra"));
662  if (exec.isEmpty()) {
663  qCWarning(KLEO_UI_LOG) << "Could not find kleopatra executable in PATH";
664  KMessageBox::error(this,
665  i18n("Could not start certificate manager; "
666  "please check your installation."),
667  i18n("Certificate Manager Error"));
668  } else {
669  QProcess::startDetached(QStringLiteral("kleopatra"), args);
670  qCDebug(KLEO_UI_LOG) << "\nslotStartCertManager(): certificate manager started.";
671  }
672 }
673 
674 #ifndef __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
675 #define __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
676 static void showKeyListError(QWidget *parent, const GpgME::Error &err)
677 {
678  Q_ASSERT(err);
679  const QString msg = i18n(
680  "<qt><p>An error occurred while fetching "
681  "the keys from the backend:</p>"
682  "<p><b>%1</b></p></qt>",
683  Formatting::errorAsString(err));
684 
685  KMessageBox::error(parent, msg, i18n("Key Listing Failed"));
686 }
687 #endif // __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
688 
689 namespace
690 {
691 struct ExtractFingerprint {
692  QString operator()(const GpgME::Key &key)
693  {
694  return QLatin1StringView(key.primaryFingerprint());
695  }
696 };
697 }
698 
699 void KeySelectionDialog::startKeyListJobForBackend(const QGpgME::Protocol *backend, const std::vector<GpgME::Key> &keys, bool validate)
700 {
701  Q_ASSERT(backend);
702  QGpgME::KeyListJob *job = backend->keyListJob(false, false, validate); // local, w/o sigs, validation as given
703  if (!job) {
704  return;
705  }
706 
707  connect(job, &QGpgME::KeyListJob::result, this, &KeySelectionDialog::slotKeyListResult);
708  if (validate) {
709  connect(job, &QGpgME::KeyListJob::nextKey, mKeyListView, &KeyListView::slotRefreshKey);
710  } else {
711  connect(job, &QGpgME::KeyListJob::nextKey, mKeyListView, &KeyListView::slotAddKey);
712  }
713 
714  QStringList fprs;
715  std::transform(keys.begin(), keys.end(), std::back_inserter(fprs), ExtractFingerprint());
716  const GpgME::Error err = job->start(fprs, mKeyUsage & SecretKeys && !(mKeyUsage & PublicKeys));
717 
718  if (err) {
719  return showKeyListError(this, err);
720  }
721 
722 #ifndef LIBKLEO_NO_PROGRESSDIALOG
723  // FIXME: create a MultiProgressDialog:
724  (void)new ProgressDialog(job, validate ? i18n("Checking selected keys...") : i18n("Fetching keys..."), this);
725 #endif
726  ++mListJobCount;
727 }
728 
729 static void selectKeys(KeyListView *klv, const std::vector<GpgME::Key> &selectedKeys)
730 {
731  klv->clearSelection();
732  if (selectedKeys.empty()) {
733  return;
734  }
735  for (auto it = selectedKeys.begin(); it != selectedKeys.end(); ++it) {
736  if (KeyListViewItem *item = klv->itemByFingerprint(it->primaryFingerprint())) {
737  item->setSelected(true);
738  }
739  }
740 }
741 
742 void KeySelectionDialog::slotKeyListResult(const GpgME::KeyListResult &res)
743 {
744  if (res.error()) {
745  showKeyListError(this, res.error());
746  } else if (res.isTruncated()) {
747  ++mTruncated;
748  }
749 
750  if (--mListJobCount > 0) {
751  return; // not yet finished...
752  }
753 
754  if (mTruncated > 0) {
756  i18np("<qt>One backend returned truncated output.<p>"
757  "Not all available keys are shown</p></qt>",
758  "<qt>%1 backends returned truncated output.<p>"
759  "Not all available keys are shown</p></qt>",
760  mTruncated),
761  i18n("Key List Result"));
762  }
763 
764  mKeyListView->flushKeys();
765 
766  mKeyListView->setEnabled(true);
767  mListJobCount = mTruncated = 0;
768  mKeysToCheck.clear();
769 
770  selectKeys(mKeyListView, mSelectedKeys);
771 
772  slotFilter();
773 
774  connectSignals();
775 
776  slotSelectionChanged();
777 
778  // restore the saved position of the contents
779  mKeyListView->verticalScrollBar()->setValue(mSavedOffsetY);
780  mSavedOffsetY = 0;
781 }
782 
783 void KeySelectionDialog::slotSelectionChanged()
784 {
785  qCDebug(KLEO_UI_LOG) << "KeySelectionDialog::slotSelectionChanged()";
786 
787  // (re)start the check selection timer. Checking the selection is delayed
788  // because else drag-selection doesn't work very good (checking key trust
789  // is slow).
790  mCheckSelectionTimer->start(sCheckSelectionDelay);
791 }
792 
793 namespace
794 {
795 struct AlreadyChecked {
796  bool operator()(const GpgME::Key &key) const
797  {
798  return key.keyListMode() & GpgME::Validate;
799  }
800 };
801 }
802 
803 void KeySelectionDialog::slotCheckSelection(KeyListViewItem *item)
804 {
805  qCDebug(KLEO_UI_LOG) << "KeySelectionDialog::slotCheckSelection()";
806 
807  mCheckSelectionTimer->stop();
808 
809  mSelectedKeys.clear();
810 
811  if (!mKeyListView->isMultiSelection()) {
812  if (item) {
813  mSelectedKeys.push_back(item->key());
814  }
815  }
816 
817  for (KeyListViewItem *it = mKeyListView->firstChild(); it; it = it->nextSibling()) {
818  if (it->isSelected()) {
819  mSelectedKeys.push_back(it->key());
820  }
821  }
822 
823  mKeysToCheck.clear();
824  std::remove_copy_if(mSelectedKeys.begin(), mSelectedKeys.end(), std::back_inserter(mKeysToCheck), AlreadyChecked());
825  if (mKeysToCheck.empty()) {
826  mOkButton->setEnabled(!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage));
827  return;
828  }
829 
830  // performed all fast checks - now for validating key listing:
831  startValidatingKeyListing();
832 }
833 
834 void KeySelectionDialog::startValidatingKeyListing()
835 {
836  if (mKeysToCheck.empty()) {
837  return;
838  }
839 
840  mListJobCount = 0;
841  mTruncated = 0;
842  mSavedOffsetY = mKeyListView->verticalScrollBar()->value();
843 
844  disconnectSignals();
845  mKeyListView->setEnabled(false);
846 
847  std::vector<GpgME::Key> smime;
848  std::vector<GpgME::Key> openpgp;
849  for (std::vector<GpgME::Key>::const_iterator it = mKeysToCheck.begin(); it != mKeysToCheck.end(); ++it) {
850  if (it->protocol() == GpgME::OpenPGP) {
851  openpgp.push_back(*it);
852  } else {
853  smime.push_back(*it);
854  }
855  }
856 
857  if (!openpgp.empty()) {
858  Q_ASSERT(mOpenPGPBackend);
859  startKeyListJobForBackend(mOpenPGPBackend, openpgp, true /*validate*/);
860  }
861  if (!smime.empty()) {
862  Q_ASSERT(mSMIMEBackend);
863  startKeyListJobForBackend(mSMIMEBackend, smime, true /*validate*/);
864  }
865 
866  Q_ASSERT(mListJobCount > 0);
867 }
868 
869 bool KeySelectionDialog::rememberSelection() const
870 {
871  return mRememberCB && mRememberCB->isChecked();
872 }
873 
874 void KeySelectionDialog::slotRMB(KeyListViewItem *item, const QPoint &p)
875 {
876  if (!item) {
877  return;
878  }
879 
880  mCurrentContextMenuItem = item;
881 
882  QMenu menu;
883  menu.addAction(i18n("Recheck Key"), this, &KeySelectionDialog::slotRecheckKey);
884  menu.exec(p);
885 }
886 
887 void KeySelectionDialog::slotRecheckKey()
888 {
889  if (!mCurrentContextMenuItem || mCurrentContextMenuItem->key().isNull()) {
890  return;
891  }
892 
893  mKeysToCheck.clear();
894  mKeysToCheck.push_back(mCurrentContextMenuItem->key());
895 }
896 
897 void KeySelectionDialog::slotTryOk()
898 {
899  if (!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)) {
900  slotOk();
901  }
902 }
903 
904 void KeySelectionDialog::slotOk()
905 {
906  if (mCheckSelectionTimer->isActive()) {
907  slotCheckSelection();
908  }
909 #if 0 // Laurent I don't understand why we returns here.
910  // button could be disabled again after checking the selected key1
911  if (!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)) {
912  return;
913  }
914 #endif
915  mStartSearchTimer->stop();
916  accept();
917 }
918 
919 void KeySelectionDialog::slotCancel()
920 {
921  mCheckSelectionTimer->stop();
922  mStartSearchTimer->stop();
923  reject();
924 }
925 
926 void KeySelectionDialog::slotSearch(const QString &text)
927 {
928  mSearchText = text.trimmed().toUpper();
929  slotSearch();
930 }
931 
932 void KeySelectionDialog::slotSearch()
933 {
934  mStartSearchTimer->setSingleShot(true);
935  mStartSearchTimer->start(sCheckSelectionDelay);
936 }
937 
938 void KeySelectionDialog::slotFilter()
939 {
940  if (mSearchText.isEmpty()) {
941  showAllItems();
942  return;
943  }
944 
945  // OK, so we need to filter:
946  QRegularExpression keyIdRegExp(QRegularExpression::anchoredPattern(QLatin1StringView("(?:0x)?[A-F0-9]{1,8}")), QRegularExpression::CaseInsensitiveOption);
947  if (keyIdRegExp.match(mSearchText).hasMatch()) {
948  if (mSearchText.startsWith(QLatin1StringView("0X")))
949  // search for keyID only:
950  {
951  filterByKeyID(mSearchText.mid(2));
952  } else
953  // search for UID and keyID:
954  {
955  filterByKeyIDOrUID(mSearchText);
956  }
957  } else {
958  // search in UID:
959  filterByUID(mSearchText);
960  }
961 }
962 
963 void KeySelectionDialog::filterByKeyID(const QString &keyID)
964 {
965  Q_ASSERT(keyID.length() <= 8);
966  Q_ASSERT(!keyID.isEmpty()); // regexp in slotFilter should prevent these
967  if (keyID.isEmpty()) {
968  showAllItems();
969  } else {
970  for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
971  item->setHidden(!item->text(0).toUpper().startsWith(keyID));
972  }
973  }
974 }
975 
976 static bool anyUIDMatches(const KeyListViewItem *item, const QRegularExpression &rx)
977 {
978  if (!item) {
979  return false;
980  }
981 
982  const std::vector<GpgME::UserID> uids = item->key().userIDs();
983  for (auto it = uids.begin(); it != uids.end(); ++it) {
984  if (it->id() && rx.match(QString::fromUtf8(it->id())).hasMatch()) {
985  return true;
986  }
987  }
988  return false;
989 }
990 
991 void KeySelectionDialog::filterByKeyIDOrUID(const QString &str)
992 {
993  Q_ASSERT(!str.isEmpty());
994 
995  // match beginnings of words:
997 
998  for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
999  item->setHidden(!item->text(0).toUpper().startsWith(str) && !anyUIDMatches(item, rx));
1000  }
1001 }
1002 
1003 void KeySelectionDialog::filterByUID(const QString &str)
1004 {
1005  Q_ASSERT(!str.isEmpty());
1006 
1007  // match beginnings of words:
1009 
1010  for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
1011  item->setHidden(!anyUIDMatches(item, rx));
1012  }
1013 }
1014 
1015 void KeySelectionDialog::showAllItems()
1016 {
1017  for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
1018  item->setHidden(false);
1019  }
1020 }
1021 
1022 #include "moc_keyselectiondialog.cpp"
QString anchoredPattern(const QString &expression)
AlignLeft
std::optional< QSqlQuery > query(const QString &queryStatement)
DN parser and reorderer.
Definition: dn.h:26
QString toUpper() const const
QString fromUtf8(const char *str, int size)
void setBuddy(QWidget *buddy)
QString trimmed() const const
void clicked(bool checked)
AscendingOrder
QRect boundingRect(QChar ch) const const
void push_back(const T &value)
QString escape(const QString &str)
QString xi18n(const char *text, const TYPE &arg...)
QString locate(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
void linkActivated(const QString &link)
void hide()
QString findExecutable(const QString &executableName, const QStringList &paths)
QAction * addAction(const QString &text)
QString i18n(const char *text, const TYPE &arg...)
QTestData & addRow(const char *format,...)
void textChanged(const QString &text)
void timeout()
bool isEmpty() const const
int length() const const
Q_SCRIPTABLE CaptureState status()
static KSharedConfig::Ptr openStateConfig(const QString &fileName=QString())
bool hasMatch() const const
void addButton(QAbstractButton *button, QDialogButtonBox::ButtonRole role)
void itemSelectionChanged()
Key_Return
QRegularExpressionMatch match(const QString &subject, int offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QString prettyDN() const
Definition: dn.cpp:439
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
bool startDetached(qint64 *pid)
bool isEmpty() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QPushButton * button(QDialogButtonBox::StandardButton which) const const
void init(KXmlGuiWindow *window, KGameDifficulty *difficulty=nullptr)
A progress dialog for Kleo::Jobs.
QString fromLatin1(const char *str, int size)
void clear()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
void setDefault(bool)
void information(QWidget *parent, const QString &text, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
QAction * exec()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Thu Feb 15 2024 03:56:14 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.