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
173static QString iconPath(const QString &name)
174{
175 return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("libkleopatra/pics/") + name + QStringLiteral(".png"));
176}
177
178ColumnStrategy::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
191QString 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
203int 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
216QString ColumnStrategy::text(const GpgME::Key &key, int col) const
217{
218 switch (col) {
219 case 0: {
220 if (key.keyID()) {
221 return Formatting::prettyID(key.keyID());
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
239QString ColumnStrategy::accessibleText(const GpgME::Key &key, int col) const
240{
241 switch (col) {
242 case 0: {
243 if (key.keyID()) {
244 return Formatting::accessibleHexID(key.keyID());
245 }
246 [[fallthrough]];
247 }
248 default:
249 return {};
250 }
251}
252
253QString 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 % QLatin1StringView("</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
293QIcon 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
331static const int sCheckSelectionDelay = 250;
332
333KeySelectionDialog::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
343KeySelectionDialog::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
360KeySelectionDialog::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
380KeySelectionDialog::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
398void KeySelectionDialog::setUpUI(Options options, const QString &initialQuery)
399{
400 auto mainLayout = new QVBoxLayout(this);
402 mOkButton = buttonBox->button(QDialogButtonBox::Ok);
403 mOkButton->setDefault(true);
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(i18nc("@action:button", "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(i18nc("@label:textbox", "&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) {
461 }
462 mTopLayout->addWidget(mKeyListView, 10);
463
464 if (options & RememberChoice) {
465 mRememberCB = new QCheckBox(i18nc("@option:check", "&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(i18nc("@action:button", "&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(i18nc("@action:button", "&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
510void 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
529KeySelectionDialog::~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
538void KeySelectionDialog::setText(const QString &text)
539{
540 mTextLabel->setText(text);
541 mTextLabel->setVisible(!text.isEmpty());
542}
543
544void KeySelectionDialog::setKeys(const std::vector<GpgME::Key> &keys)
545{
546 for (const GpgME::Key &key : keys) {
547 mKeyListView->slotAddKey(key);
548 }
549}
550
551void 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
563void 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
575const 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
584QString KeySelectionDialog::fingerprint() const
585{
586 return QLatin1StringView(selectedKey().primaryFingerprint());
587}
588
589QStringList KeySelectionDialog::fingerprints() const
590{
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
600QStringList KeySelectionDialog::pgpKeyFingerprints() const
601{
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
613QStringList KeySelectionDialog::smimeFingerprints() const
614{
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
626void 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 i18nc("@title:window", "Key Listing Failed"));
650 connectSignals();
651 }
652}
653
654void 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";
665 i18n("Could not start certificate manager; "
666 "please check your installation."),
667 i18nc("@title:window", "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__
676static 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, i18nc("@title:window", "Key Listing Failed"));
686}
687#endif // __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
688
689namespace
690{
691struct ExtractFingerprint {
692 QString operator()(const GpgME::Key &key)
693 {
694 return QLatin1StringView(key.primaryFingerprint());
695 }
696};
697}
698
699void 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
729static 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
742void 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
783void 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
793namespace
794{
795struct AlreadyChecked {
796 bool operator()(const GpgME::Key &key) const
797 {
798 return key.keyListMode() & GpgME::Validate;
799 }
800};
801}
802
803void 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
834void 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
869bool KeySelectionDialog::rememberSelection() const
870{
871 return mRememberCB && mRememberCB->isChecked();
872}
873
874void 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
887void KeySelectionDialog::slotRecheckKey()
888{
889 if (!mCurrentContextMenuItem || mCurrentContextMenuItem->key().isNull()) {
890 return;
891 }
892
893 mKeysToCheck.clear();
894 mKeysToCheck.push_back(mCurrentContextMenuItem->key());
895}
896
897void KeySelectionDialog::slotTryOk()
898{
899 if (!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)) {
900 slotOk();
901 }
902}
903
904void 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
919void KeySelectionDialog::slotCancel()
920{
921 mCheckSelectionTimer->stop();
922 mStartSearchTimer->stop();
923 reject();
924}
925
926void KeySelectionDialog::slotSearch(const QString &text)
927{
928 mSearchText = text.trimmed().toUpper();
929 slotSearch();
930}
931
932void KeySelectionDialog::slotSearch()
933{
934 mStartSearchTimer->setSingleShot(true);
935 mStartSearchTimer->start(sCheckSelectionDelay);
936}
937
938void KeySelectionDialog::slotFilter()
939{
940 if (mSearchText.isEmpty()) {
941 showAllItems();
942 return;
943 }
944
945 // OK, so we need to filter:
947 if (keyIdRegExp.match(mSearchText).hasMatch()) {
948 if (mSearchText.startsWith(QLatin1StringView("0X"))) {
949 // search for keyID only:
950 filterByKeyID(mSearchText.mid(2));
951 } else {
952 // search for UID and keyID:
953 filterByKeyIDOrUID(mSearchText);
954 }
955 } else {
956 // search in UID:
957 filterByUID(mSearchText);
958 }
959}
960
961void KeySelectionDialog::filterByKeyID(const QString &keyID)
962{
963 Q_ASSERT(keyID.length() <= 16);
964 Q_ASSERT(!keyID.isEmpty()); // regexp in slotFilter should prevent these
965 if (keyID.isEmpty()) {
966 showAllItems();
967 } else {
968 for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
969 item->setHidden(!item->text(0).remove(u' ').toUpper().startsWith(keyID));
970 }
971 }
972}
973
974static bool anyUIDMatches(const KeyListViewItem *item, const QRegularExpression &rx)
975{
976 if (!item) {
977 return false;
978 }
979
980 const std::vector<GpgME::UserID> uids = item->key().userIDs();
981 for (auto it = uids.begin(); it != uids.end(); ++it) {
982 if (it->id() && rx.match(QString::fromUtf8(it->id())).hasMatch()) {
983 return true;
984 }
985 }
986 return false;
987}
988
989void KeySelectionDialog::filterByKeyIDOrUID(const QString &str)
990{
991 Q_ASSERT(!str.isEmpty());
992
993 // match beginnings of words:
995
996 for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
997 item->setHidden(!item->text(0).remove(u' ').toUpper().startsWith(str) && !anyUIDMatches(item, rx));
998 }
999}
1000
1001void KeySelectionDialog::filterByUID(const QString &str)
1002{
1003 Q_ASSERT(!str.isEmpty());
1004
1005 // match beginnings of words:
1007
1008 for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
1009 item->setHidden(!anyUIDMatches(item, rx));
1010 }
1011}
1012
1013void KeySelectionDialog::showAllItems()
1014{
1015 for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
1016 item->setHidden(false);
1017 }
1018}
1019
1020#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:439
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...)
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
void init(KXmlGuiWindow *window, KGameDifficulty *difficulty=nullptr)
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)
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)
bool isEmpty() const const
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 locate(StandardLocation type, const QString &fileName, LocateOptions options)
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 Fri Oct 11 2024 12:11:57 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.