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

KDE's Doxygen guidelines are available online.