Libkleo

newkeyapprovaldialog.cpp
1/* -*- c++ -*-
2 newkeyapprovaldialog.cpp
3
4 This file is part of libkleopatra, the KDE keymanagement library
5 SPDX-FileCopyrightText: 2018 Intevation GmbH
6 SPDX-FileCopyrightText: 2021 g10 Code GmbH
7 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
8
9 SPDX-License-Identifier: GPL-2.0-or-later
10*/
11
12#include <config-libkleo.h>
13
14#include "newkeyapprovaldialog.h"
15
16#include "keyselectioncombo.h"
17#include "progressdialog.h"
18
19#include <libkleo/adjustingscrollarea.h>
20#include <libkleo/algorithm.h>
21#include <libkleo/compliance.h>
22#include <libkleo/debug.h>
23#include <libkleo/defaultkeyfilter.h>
24#include <libkleo/formatting.h>
25#include <libkleo/gnupg.h>
26#include <libkleo/keyhelpers.h>
27#include <libkleo/systeminfo.h>
28
29#include <libkleo_debug.h>
30
31#include <KColorScheme>
32#include <KLocalizedString>
33#include <KMessageBox>
34
35#include <QGpgME/Protocol>
36#include <QGpgME/QuickJob>
37
38#include <QButtonGroup>
39#include <QCheckBox>
40#include <QDialogButtonBox>
41#include <QGroupBox>
42#include <QHBoxLayout>
43#include <QLabel>
44#include <QMap>
45#include <QPushButton>
46#include <QRadioButton>
47#include <QScreen>
48#include <QToolTip>
49#include <QVBoxLayout>
50
51#include <gpgme++/context.h>
52#include <gpgme++/key.h>
53#include <gpgme++/keygenerationresult.h>
54
55using namespace Kleo;
56using namespace GpgME;
57
58namespace
59{
60class EncryptFilter : public DefaultKeyFilter
61{
62public:
63 EncryptFilter()
65 {
66 setHasEncrypt(DefaultKeyFilter::Set);
67 }
68};
69static std::shared_ptr<KeyFilter> s_encryptFilter = std::shared_ptr<KeyFilter>(new EncryptFilter);
70
71class OpenPGPFilter : public DefaultKeyFilter
72{
73public:
74 OpenPGPFilter()
76 {
77 setIsOpenPGP(DefaultKeyFilter::Set);
78 setHasEncrypt(DefaultKeyFilter::Set);
79 }
80};
81static std::shared_ptr<KeyFilter> s_pgpEncryptFilter = std::shared_ptr<KeyFilter>(new OpenPGPFilter);
82
83class OpenPGPSignFilter : public DefaultKeyFilter
84{
85public:
86 OpenPGPSignFilter()
88 {
89 /* Also list unusable keys to make it transparent why they are unusable */
90 setDisabled(DefaultKeyFilter::NotSet);
91 setRevoked(DefaultKeyFilter::NotSet);
92 setExpired(DefaultKeyFilter::NotSet);
93 setCanSign(DefaultKeyFilter::Set);
94 setHasSecret(DefaultKeyFilter::Set);
95 setIsOpenPGP(DefaultKeyFilter::Set);
96 }
97};
98static std::shared_ptr<KeyFilter> s_pgpSignFilter = std::shared_ptr<KeyFilter>(new OpenPGPSignFilter);
99
100class SMIMEFilter : public DefaultKeyFilter
101{
102public:
103 SMIMEFilter()
105 {
106 setIsOpenPGP(DefaultKeyFilter::NotSet);
107 setHasEncrypt(DefaultKeyFilter::Set);
108 }
109};
110static std::shared_ptr<KeyFilter> s_smimeEncryptFilter = std::shared_ptr<KeyFilter>(new SMIMEFilter);
111
112class SMIMESignFilter : public DefaultKeyFilter
113{
114public:
115 SMIMESignFilter()
117 {
118 setDisabled(DefaultKeyFilter::NotSet);
119 setRevoked(DefaultKeyFilter::NotSet);
120 setExpired(DefaultKeyFilter::NotSet);
121 setCanSign(DefaultKeyFilter::Set);
122 setIsOpenPGP(DefaultKeyFilter::NotSet);
123 setHasSecret(DefaultKeyFilter::Set);
124 }
125};
126static std::shared_ptr<KeyFilter> s_smimeSignFilter = std::shared_ptr<KeyFilter>(new SMIMESignFilter);
127
128/* Some decoration and a button to remove the filter for a keyselectioncombo */
129class ComboWidget : public QWidget
130{
131 Q_OBJECT
132public:
133 explicit ComboWidget(KeySelectionCombo *combo)
134 : mCombo(combo)
135 , mFilterBtn(new QPushButton)
136 {
137 auto hLay = new QHBoxLayout(this);
138 auto infoBtn = new QPushButton;
139 infoBtn->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual")));
140 infoBtn->setIconSize(QSize(22, 22));
141 infoBtn->setFlat(true);
142 infoBtn->setAccessibleName(i18nc("@action:button", "Show Details"));
143 hLay->addWidget(infoBtn);
144 hLay->addWidget(combo, 1);
145 hLay->addWidget(mFilterBtn, 0);
146
147 connect(infoBtn, &QPushButton::clicked, this, [this, infoBtn]() {
148 QToolTip::showText(infoBtn->mapToGlobal(QPoint()) + QPoint(infoBtn->width(), 0),
149 mCombo->currentData(Qt::ToolTipRole).toString(),
150 infoBtn,
151 QRect(),
152 30000);
153 });
154
155 // FIXME: This is ugly to enforce but otherwise the
156 // icon is broken.
157 combo->setMinimumHeight(22);
158 mFilterBtn->setMinimumHeight(23);
159
160 updateFilterButton();
161
162 connect(mFilterBtn, &QPushButton::clicked, this, [this]() {
163 const QString curFilter = mCombo->idFilter();
164 if (curFilter.isEmpty()) {
165 setIdFilter(mLastIdFilter);
166 mLastIdFilter = QString();
167 } else {
168 setIdFilter(QString());
169 mLastIdFilter = curFilter;
170 }
171 });
172 }
173
174 void setIdFilter(const QString &id)
175 {
176 mCombo->setIdFilter(id);
177 updateFilterButton();
178 }
179
180 void updateFilterButton()
181 {
182 if (mCombo->idFilter().isEmpty()) {
183 mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-add-filters")));
184 mFilterBtn->setAccessibleName(i18nc("@action:button", "Show Matching Keys"));
185 mFilterBtn->setToolTip(i18nc("@info:tooltip", "Show keys matching the email address"));
186 } else {
187 mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters")));
188 mFilterBtn->setAccessibleName(i18nc("@action:button short for 'Show all keys'", "Show All"));
189 mFilterBtn->setToolTip(i18nc("@info:tooltip", "Show all keys"));
190 }
191 }
192
193 KeySelectionCombo *combo()
194 {
195 return mCombo;
196 }
197
198 GpgME::Protocol fixedProtocol() const
199 {
200 return mFixedProtocol;
201 }
202
203 void setFixedProtocol(GpgME::Protocol proto)
204 {
205 mFixedProtocol = proto;
206 }
207
208private:
209 KeySelectionCombo *mCombo;
210 QPushButton *mFilterBtn;
211 QString mLastIdFilter;
212 GpgME::Protocol mFixedProtocol = GpgME::UnknownProtocol;
213};
214
215static bool key_has_addr(const GpgME::Key &key, const QString &addr)
216{
217 for (const auto &uid : key.userIDs()) {
218 if (QString::fromStdString(uid.addrSpec()).toLower() == addr.toLower()) {
219 return true;
220 }
221 }
222 return false;
223}
224
225Key findfirstKeyOfType(const std::vector<Key> &keys, GpgME::Protocol protocol)
226{
227 const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol](const auto &key) {
228 return key.protocol() == protocol;
229 });
230 return it != std::end(keys) ? *it : Key();
231}
232
233} // namespace
234
235class NewKeyApprovalDialog::Private
236{
237private:
238 enum Action {
239 Unset,
240 GenerateKey,
241 IgnoreKey,
242 };
243
244public:
245 enum {
246 OpenPGPButtonId = 1,
247 SMIMEButtonId = 2,
248 };
249
250 Private(NewKeyApprovalDialog *qq,
251 bool encrypt,
252 bool sign,
253 GpgME::Protocol forcedProtocol,
254 GpgME::Protocol presetProtocol,
255 const QString &sender,
256 bool allowMixed)
257 : mForcedProtocol{forcedProtocol}
258 , mSender{sender}
259 , mSign{sign}
260 , mEncrypt{encrypt}
261 , mAllowMixed{allowMixed}
262 , q{qq}
263 {
264 Q_ASSERT(forcedProtocol == GpgME::UnknownProtocol || presetProtocol == GpgME::UnknownProtocol || presetProtocol == forcedProtocol);
265 Q_ASSERT(!allowMixed || (allowMixed && forcedProtocol == GpgME::UnknownProtocol));
266 Q_ASSERT(!(!allowMixed && presetProtocol == GpgME::UnknownProtocol));
267
268 // We do the translation here to avoid having the same string multiple times.
269 mGenerateTooltip = i18nc(
270 "@info:tooltip for a 'Generate new key pair' action "
271 "in a combobox when a user does not yet have an OpenPGP or S/MIME key.",
272 "Generate a new key using your email address.<br/><br/>"
273 "The key is necessary to decrypt and sign emails. "
274 "You will be asked for a passphrase to protect this key and the protected key "
275 "will be stored in your home directory.");
276 mMainLay = new QVBoxLayout;
277
279 mOkButton = btnBox->button(QDialogButtonBox::Ok);
280#ifndef NDEBUG
281 mOkButton->setObjectName(QLatin1StringView("ok button"));
282#endif
283 QObject::connect(btnBox, &QDialogButtonBox::accepted, q, [this]() {
284 accepted();
285 });
287
288 mScrollArea = new AdjustingScrollArea;
289 mScrollArea->setWidget(new QWidget);
290 mScrollLayout = new QVBoxLayout;
291 mScrollArea->widget()->setLayout(mScrollLayout);
292 mScrollArea->setWidgetResizable(true);
294 mScrollArea->setFrameStyle(QFrame::NoFrame);
295 mScrollLayout->setContentsMargins(0, 0, 0, 0);
296
297 q->setWindowTitle(i18nc("@title:window", "Security approval"));
298
299 auto fmtLayout = new QHBoxLayout;
300 mFormatBtns = new QButtonGroup(qq);
301 QAbstractButton *pgpBtn;
302 QAbstractButton *smimeBtn;
303 if (mAllowMixed) {
304 pgpBtn = new QCheckBox(i18nc("@option:check", "OpenPGP"));
305 smimeBtn = new QCheckBox(i18nc("@option:check", "S/MIME"));
306 } else {
307 pgpBtn = new QRadioButton(i18nc("@option:radio", "OpenPGP"));
308 smimeBtn = new QRadioButton(i18nc("@option:radio", "S/MIME"));
309 }
310#ifndef NDEBUG
311 pgpBtn->setObjectName(QLatin1StringView("openpgp button"));
312 smimeBtn->setObjectName(QLatin1StringView("smime button"));
313#endif
314 mFormatBtns->addButton(pgpBtn, OpenPGPButtonId);
315 mFormatBtns->addButton(smimeBtn, SMIMEButtonId);
316 mFormatBtns->setExclusive(!mAllowMixed);
317
318 connect(mFormatBtns, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked), q, [this]() {
319 updateOkButton();
320 });
321
322 fmtLayout->addStretch(-1);
323 fmtLayout->addWidget(pgpBtn);
324 fmtLayout->addWidget(smimeBtn);
325 mMainLay->addLayout(fmtLayout);
326
327 if (mForcedProtocol != GpgME::UnknownProtocol) {
328 pgpBtn->setChecked(mForcedProtocol == GpgME::OpenPGP);
329 smimeBtn->setChecked(mForcedProtocol == GpgME::CMS);
330 pgpBtn->setVisible(false);
331 smimeBtn->setVisible(false);
332 } else {
333 pgpBtn->setChecked(presetProtocol == GpgME::OpenPGP || presetProtocol == GpgME::UnknownProtocol);
334 smimeBtn->setChecked(presetProtocol == GpgME::CMS || presetProtocol == GpgME::UnknownProtocol);
335 }
336
337 QObject::connect(mFormatBtns, &QButtonGroup::idClicked, q, [this](int buttonId) {
338 // ensure that at least one protocol button is checked
339 if (mAllowMixed && !mFormatBtns->button(OpenPGPButtonId)->isChecked() && !mFormatBtns->button(SMIMEButtonId)->isChecked()) {
340 mFormatBtns->button(buttonId == OpenPGPButtonId ? SMIMEButtonId : OpenPGPButtonId)->setChecked(true);
341 }
342 updateWidgets();
343 });
344
345 mMainLay->addWidget(mScrollArea);
346
347 mComplianceLbl = new QLabel;
348 mComplianceLbl->setVisible(false);
349#ifndef NDEBUG
350 mComplianceLbl->setObjectName(QLatin1StringView("compliance label"));
351#endif
352
353 auto btnLayout = new QHBoxLayout;
354 btnLayout->addWidget(mComplianceLbl);
355 btnLayout->addWidget(btnBox);
356 mMainLay->addLayout(btnLayout);
357
358 q->setLayout(mMainLay);
359 }
360
361 ~Private() = default;
362
363 GpgME::Protocol currentProtocol()
364 {
365 const bool openPGPButtonChecked = mFormatBtns->button(OpenPGPButtonId)->isChecked();
366 const bool smimeButtonChecked = mFormatBtns->button(SMIMEButtonId)->isChecked();
367 if (mAllowMixed) {
368 if (openPGPButtonChecked && !smimeButtonChecked) {
369 return GpgME::OpenPGP;
370 }
371 if (!openPGPButtonChecked && smimeButtonChecked) {
372 return GpgME::CMS;
373 }
374 } else if (openPGPButtonChecked) {
375 return GpgME::OpenPGP;
376 } else if (smimeButtonChecked) {
377 return GpgME::CMS;
378 }
379 return GpgME::UnknownProtocol;
380 }
381
382 auto findVisibleKeySelectionComboWithGenerateKey()
383 {
384 const auto it = std::find_if(std::begin(mAllCombos), std::end(mAllCombos), [](auto combo) {
385 return combo->isVisible() && combo->currentData(Qt::UserRole).toInt() == GenerateKey;
386 });
387 return it != std::end(mAllCombos) ? *it : nullptr;
388 }
389
390 void generateKey(KeySelectionCombo *combo)
391 {
392 if (!mRunningJobs.empty()) {
393 return;
394 }
395 const auto &addr = combo->property("address").toString();
396 auto job = QGpgME::openpgp()->quickJob();
397 auto progress =
398 new Kleo::ProgressDialog(job, i18n("Generating key for '%1'...", addr) + QStringLiteral("\n\n") + i18n("This can take several minutes."), q);
399 progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowContextHelpButtonHint);
400 progress->setWindowTitle(i18nc("@title:window", "Key generation"));
401 progress->setModal(true);
402 progress->setAutoClose(true);
403 progress->setMinimumDuration(0);
404 progress->setValue(0);
405
406 mRunningJobs << job;
407 if (!connect(job, &QGpgME::QuickJob::result, q, [this, job, combo]() {
408 handleKeyGenResult(QGpgME::Job::context(job)->keyGenerationResult(), job, combo);
409 })) {
410 qCWarning(LIBKLEO_LOG) << "new-style connect failed; connecting to QGpgME::QuickJob::result the old way";
411 connect(job, SIGNAL(result(const GpgME::Error &)), q, SLOT(handleKeyGenResult()));
412 }
413 job->startCreate(addr, nullptr);
414 return;
415 }
416
417 void handleKeyGenResult(const GpgME::KeyGenerationResult &result, QGpgME::Job *job, KeySelectionCombo *combo)
418 {
419 mLastError = result.error();
420 if (!mLastError) {
421 connect(combo, &KeySelectionCombo::keyListingFinished, q, [this, job]() {
422 mRunningJobs.removeAll(job);
423 });
424 // update all combos showing the GenerateKey item
425 for (auto c : std::as_const(mAllCombos)) {
426 if (c->currentData(Qt::UserRole).toInt() == GenerateKey) {
427 c->setDefaultKey(QString::fromLatin1(result.fingerprint()), GpgME::OpenPGP);
428 c->refreshKeys();
429 }
430 }
431 } else {
432 mRunningJobs.removeAll(job);
433 }
434 }
435
436 void checkAccepted()
437 {
438 if (mLastError) {
439 KMessageBox::error(q, Formatting::errorAsString(mLastError), i18nc("@title:window", "Operation Failed"));
440 mRunningJobs.clear();
441 return;
442 }
443
444 if (!mRunningJobs.empty()) {
445 return;
446 }
447
448 /* Save the keys */
449 mAcceptedResult.protocol = currentProtocol();
450 for (const auto combo : std::as_const(mEncCombos)) {
451 const auto addr = combo->property("address").toString();
452 const auto key = combo->currentKey();
453 if (!combo->isVisible() || key.isNull()) {
454 continue;
455 }
456 mAcceptedResult.encryptionKeys[addr].push_back(key);
457 }
458 for (const auto combo : std::as_const(mSigningCombos)) {
459 const auto key = combo->currentKey();
460 if (!combo->isVisible() || key.isNull()) {
461 continue;
462 }
463 mAcceptedResult.signingKeys.push_back(key);
464 }
465
466 q->accept();
467 }
468
469 void accepted()
470 {
471 // We can assume everything was validly resolved, otherwise
472 // the OK button would have been disabled.
473 // Handle custom items now.
474 if (auto combo = findVisibleKeySelectionComboWithGenerateKey()) {
475 generateKey(combo);
476 return;
477 }
478 checkAccepted();
479 }
480
481 auto encryptionKeyFilter(GpgME::Protocol protocol)
482 {
483 switch (protocol) {
484 case OpenPGP:
485 return s_pgpEncryptFilter;
486 case CMS:
487 return s_smimeEncryptFilter;
488 default:
489 return s_encryptFilter;
490 }
491 }
492
493 void updateWidgets()
494 {
495 const GpgME::Protocol protocol = currentProtocol();
496 const auto encryptionFilter = encryptionKeyFilter(protocol);
497
498 for (auto combo : std::as_const(mSigningCombos)) {
499 auto widget = qobject_cast<ComboWidget *>(combo->parentWidget());
500 if (!widget) {
501 qCDebug(LIBKLEO_LOG) << "Failed to find signature combo widget";
502 continue;
503 }
504 widget->setVisible(protocol == GpgME::UnknownProtocol || widget->fixedProtocol() == GpgME::UnknownProtocol || widget->fixedProtocol() == protocol);
505 }
506 for (auto combo : std::as_const(mEncCombos)) {
507 auto widget = qobject_cast<ComboWidget *>(combo->parentWidget());
508 if (!widget) {
509 qCDebug(LIBKLEO_LOG) << "Failed to find combo widget";
510 continue;
511 }
512 widget->setVisible(protocol == GpgME::UnknownProtocol || widget->fixedProtocol() == GpgME::UnknownProtocol || widget->fixedProtocol() == protocol);
513 if (widget->isVisible() && combo->property("address") != mSender) {
514 combo->setKeyFilter(encryptionFilter);
515 }
516 }
517 // hide the labels indicating the protocol of the sender's keys if only a single protocol is active
518 const auto protocolLabels = q->findChildren<QLabel *>(QStringLiteral("protocol label"));
519 for (auto label : protocolLabels) {
520 label->setVisible(protocol == GpgME::UnknownProtocol);
521 }
522 }
523
524 auto createProtocolLabel(GpgME::Protocol protocol)
525 {
526 auto label = new QLabel(Formatting::displayName(protocol));
527 label->setObjectName(QLatin1StringView("protocol label"));
528 return label;
529 }
530
531 ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key, GpgME::Protocol protocol = GpgME::UnknownProtocol)
532 {
533 Q_ASSERT(!key.isNull() || protocol != UnknownProtocol);
534 protocol = !key.isNull() ? key.protocol() : protocol;
535
536 auto combo = new KeySelectionCombo{true, KeyUsage::Sign};
537 auto comboWidget = new ComboWidget(combo);
538#ifndef NDEBUG
539 combo->setObjectName(QLatin1StringView("signing key"));
540#endif
541 if (protocol == GpgME::OpenPGP) {
542 combo->setKeyFilter(s_pgpSignFilter);
543 } else if (protocol == GpgME::CMS) {
544 combo->setKeyFilter(s_smimeSignFilter);
545 }
546 if (key.isNull() || key_has_addr(key, mSender)) {
547 comboWidget->setIdFilter(mSender);
548 }
549 comboWidget->setFixedProtocol(protocol);
550 if (!key.isNull()) {
551 combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), protocol);
552 }
553 if (key.isNull() && protocol == OpenPGP) {
554 combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey, mGenerateTooltip);
555 }
556 combo->appendCustomItem(Formatting::unavailableIcon(),
557 i18n("Do not sign this email"),
558 IgnoreKey,
559 i18nc("@info:tooltip for not selecting a key for signing.", "The email will not be cryptographically signed."));
560
561 mSigningCombos << combo;
562 mAllCombos << combo;
563 combo->setProperty("address", addr);
564
565 connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this]() {
566 updateOkButton();
567 });
568 connect(combo, qOverload<int>(&QComboBox::currentIndexChanged), q, [this]() {
569 updateOkButton();
570 });
571
572 return comboWidget;
573 }
574
575 void setSigningKeys(const std::vector<GpgME::Key> &preferredKeys, const std::vector<GpgME::Key> &alternativeKeys)
576 {
577 auto group = new QGroupBox(i18nc("Caption for signing key selection", "Confirm identity '%1' as:", mSender));
578 group->setAlignment(Qt::AlignLeft);
579 auto sigLayout = new QVBoxLayout(group);
580
581 const bool mayNeedOpenPGP = mForcedProtocol != CMS;
582 const bool mayNeedCMS = mForcedProtocol != OpenPGP;
583 if (mayNeedOpenPGP) {
584 if (mAllowMixed) {
585 sigLayout->addWidget(createProtocolLabel(OpenPGP));
586 }
587 const Key preferredKey = findfirstKeyOfType(preferredKeys, OpenPGP);
588 const Key alternativeKey = findfirstKeyOfType(alternativeKeys, OpenPGP);
589 if (!preferredKey.isNull()) {
590 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey;
591 auto comboWidget = createSigningCombo(mSender, preferredKey);
592 sigLayout->addWidget(comboWidget);
593 } else if (!alternativeKey.isNull()) {
594 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey;
595 auto comboWidget = createSigningCombo(mSender, alternativeKey);
596 sigLayout->addWidget(comboWidget);
597 } else {
598 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for OpenPGP key";
599 auto comboWidget = createSigningCombo(mSender, Key(), OpenPGP);
600 sigLayout->addWidget(comboWidget);
601 }
602 }
603 if (mayNeedCMS) {
604 if (mAllowMixed) {
605 sigLayout->addWidget(createProtocolLabel(CMS));
606 }
607 const Key preferredKey = findfirstKeyOfType(preferredKeys, CMS);
608 const Key alternativeKey = findfirstKeyOfType(alternativeKeys, CMS);
609 if (!preferredKey.isNull()) {
610 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey;
611 auto comboWidget = createSigningCombo(mSender, preferredKey);
612 sigLayout->addWidget(comboWidget);
613 } else if (!alternativeKey.isNull()) {
614 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey;
615 auto comboWidget = createSigningCombo(mSender, alternativeKey);
616 sigLayout->addWidget(comboWidget);
617 } else {
618 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for S/MIME key";
619 auto comboWidget = createSigningCombo(mSender, Key(), CMS);
620 sigLayout->addWidget(comboWidget);
621 }
622 }
623
624 mScrollLayout->addWidget(group);
625 }
626
627 ComboWidget *createEncryptionCombo(const QString &addr, const GpgME::Key &key, GpgME::Protocol fixedProtocol)
628 {
629 auto combo = new KeySelectionCombo{false, KeyUsage::Encrypt};
630 auto comboWidget = new ComboWidget(combo);
631#ifndef NDEBUG
632 combo->setObjectName(QLatin1StringView("encryption key"));
633#endif
634 if (fixedProtocol == GpgME::OpenPGP) {
635 combo->setKeyFilter(s_pgpEncryptFilter);
636 } else if (fixedProtocol == GpgME::CMS) {
637 combo->setKeyFilter(s_smimeEncryptFilter);
638 } else {
639 combo->setKeyFilter(s_encryptFilter);
640 }
641 if (key.isNull() || key_has_addr(key, addr)) {
642 comboWidget->setIdFilter(addr);
643 }
644 comboWidget->setFixedProtocol(fixedProtocol);
645 if (!key.isNull()) {
646 combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), fixedProtocol);
647 }
648
649 if (addr == mSender && key.isNull() && fixedProtocol == OpenPGP) {
650 combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey, mGenerateTooltip);
651 }
652
653 combo->appendCustomItem(Formatting::unavailableIcon(),
654 i18n("No key. Recipient will be unable to decrypt."),
655 IgnoreKey,
656 i18nc("@info:tooltip for No Key selected for a specific recipient.",
657 "Do not select a key for this recipient.<br/><br/>"
658 "The recipient will receive the encrypted email, but it can only "
659 "be decrypted with the other keys selected in this dialog."));
660
661 connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this]() {
662 updateOkButton();
663 });
664 connect(combo, qOverload<int>(&QComboBox::currentIndexChanged), q, [this]() {
665 updateOkButton();
666 });
667
668 mEncCombos << combo;
669 mAllCombos << combo;
670 combo->setProperty("address", addr);
671 return comboWidget;
672 }
673
674 void addEncryptionAddr(const QString &addr,
675 GpgME::Protocol preferredKeysProtocol,
676 const std::vector<GpgME::Key> &preferredKeys,
677 GpgME::Protocol alternativeKeysProtocol,
678 const std::vector<GpgME::Key> &alternativeKeys,
679 QGridLayout *encGrid)
680 {
681 if (addr == mSender) {
682 const bool mayNeedOpenPGP = mForcedProtocol != CMS;
683 const bool mayNeedCMS = mForcedProtocol != OpenPGP;
684 if (mayNeedOpenPGP) {
685 if (mAllowMixed) {
686 encGrid->addWidget(createProtocolLabel(OpenPGP), encGrid->rowCount(), 0);
687 }
688 for (const auto &key : preferredKeys) {
689 if (key.protocol() == OpenPGP) {
690 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
691 auto comboWidget = createEncryptionCombo(addr, key, OpenPGP);
692 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
693 }
694 }
695 for (const auto &key : alternativeKeys) {
696 if (key.protocol() == OpenPGP) {
697 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
698 auto comboWidget = createEncryptionCombo(addr, key, OpenPGP);
699 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
700 }
701 }
702 if (!anyKeyHasProtocol(preferredKeys, OpenPGP) && !anyKeyHasProtocol(alternativeKeys, OpenPGP)) {
703 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for OpenPGP key";
704 auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), OpenPGP);
705 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
706 }
707 }
708 if (mayNeedCMS) {
709 if (mAllowMixed) {
710 encGrid->addWidget(createProtocolLabel(CMS), encGrid->rowCount(), 0);
711 }
712 for (const auto &key : preferredKeys) {
713 if (key.protocol() == CMS) {
714 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
715 auto comboWidget = createEncryptionCombo(addr, key, CMS);
716 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
717 }
718 }
719 for (const auto &key : alternativeKeys) {
720 if (key.protocol() == CMS) {
721 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
722 auto comboWidget = createEncryptionCombo(addr, key, CMS);
723 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
724 }
725 }
726 if (!anyKeyHasProtocol(preferredKeys, CMS) && !anyKeyHasProtocol(alternativeKeys, CMS)) {
727 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for S/MIME key";
728 auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), CMS);
729 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
730 }
731 }
732 } else {
733 encGrid->addWidget(new QLabel(addr), encGrid->rowCount(), 0);
734
735 for (const auto &key : preferredKeys) {
736 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
737 auto comboWidget = createEncryptionCombo(addr, key, preferredKeysProtocol);
738 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
739 }
740 for (const auto &key : alternativeKeys) {
741 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
742 auto comboWidget = createEncryptionCombo(addr, key, alternativeKeysProtocol);
743 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
744 }
745 if (!mAllowMixed) {
746 if (preferredKeys.empty()) {
747 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(preferredKeysProtocol)
748 << "key";
749 auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), preferredKeysProtocol);
750 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
751 }
752 if (alternativeKeys.empty() && alternativeKeysProtocol != UnknownProtocol) {
753 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for"
754 << Formatting::displayName(alternativeKeysProtocol) << "key";
755 auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), alternativeKeysProtocol);
756 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
757 }
758 } else {
759 if (preferredKeys.empty() && alternativeKeys.empty()) {
760 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for any key";
761 auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), UnknownProtocol);
762 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
763 }
764 }
765 }
766 }
767
768 void setEncryptionKeys(GpgME::Protocol preferredKeysProtocol,
769 const QMap<QString, std::vector<GpgME::Key>> &preferredKeys,
770 GpgME::Protocol alternativeKeysProtocol,
771 const QMap<QString, std::vector<GpgME::Key>> &alternativeKeys)
772 {
773 {
774 auto group = new QGroupBox(i18nc("Encrypt to self (email address):", "Encrypt to self (%1):", mSender));
775#ifndef NDEBUG
776 group->setObjectName(QLatin1StringView("encrypt-to-self box"));
777#endif
778 group->setAlignment(Qt::AlignLeft);
779 auto encGrid = new QGridLayout(group);
780
781 addEncryptionAddr(mSender, preferredKeysProtocol, preferredKeys.value(mSender), alternativeKeysProtocol, alternativeKeys.value(mSender), encGrid);
782
783 encGrid->setColumnStretch(1, -1);
784 mScrollLayout->addWidget(group);
785 }
786
787 const bool hasOtherRecipients = std::any_of(preferredKeys.keyBegin(), preferredKeys.keyEnd(), [this](const auto &recipient) {
788 return recipient != mSender;
789 });
790 if (hasOtherRecipients) {
791 auto group = new QGroupBox(i18n("Encrypt to others:"));
792#ifndef NDEBUG
793 group->setObjectName(QLatin1StringView("encrypt-to-others box"));
794#endif
795 group->setAlignment(Qt::AlignLeft);
796 auto encGrid = new QGridLayout{group};
797
798 for (auto it = std::begin(preferredKeys); it != std::end(preferredKeys); ++it) {
799 const auto &address = it.key();
800 const auto &keys = it.value();
801 if (address != mSender) {
802 addEncryptionAddr(address, preferredKeysProtocol, keys, alternativeKeysProtocol, alternativeKeys.value(address), encGrid);
803 }
804 }
805
806 encGrid->setColumnStretch(1, -1);
807 mScrollLayout->addWidget(group);
808 }
809
810 mScrollLayout->addStretch(-1);
811 }
812
813 void updateOkButton()
814 {
815 static QString origOkText = mOkButton->text();
816 const bool isGenerate = bool(findVisibleKeySelectionComboWithGenerateKey());
817 const bool allVisibleEncryptionKeysAreIgnored = Kleo::all_of(mEncCombos, [](auto combo) {
818 return !combo->isVisible() || combo->currentData(Qt::UserRole).toInt() == IgnoreKey;
819 });
820 const bool allVisibleEncryptionKeysAreUsable = Kleo::all_of(mEncCombos, [](auto combo) {
821 return !combo->isVisible() || combo->currentKey().isNull() || Kleo::canBeUsedForEncryption(combo->currentKey());
822 });
823
824 // If we don't encrypt, then the OK button is always enabled. Likewise,
825 // if the "generate key" option is selected. Otherwise,
826 // we only enable it if we encrypt to at least one recipient.
827 mOkButton->setEnabled(isGenerate || !mEncrypt || (!allVisibleEncryptionKeysAreIgnored && allVisibleEncryptionKeysAreUsable));
828
829 mOkButton->setText(isGenerate ? i18n("Generate") : origOkText);
830
831 if (!DeVSCompliance::isActive()) {
832 return;
833 }
834
835 // Handle compliance
836 bool de_vs = DeVSCompliance::isCompliant();
837
838 if (de_vs) {
839 const GpgME::Protocol protocol = currentProtocol();
840
841 for (const auto combo : std::as_const(mAllCombos)) {
842 if (!combo->isVisible()) {
843 continue;
844 }
845 const auto key = combo->currentKey();
846 if (key.isNull()) {
847 continue;
848 }
849 if (protocol != GpgME::UnknownProtocol && key.protocol() != protocol) {
850 continue;
851 }
852 if (!DeVSCompliance::keyIsCompliant(key)) {
853 de_vs = false;
854 break;
855 }
856 }
857 }
858
859 const auto doNotSign = Kleo::any_of(mSigningCombos, [](const auto &combo) {
860 return combo->isVisible() && combo->currentData() == IgnoreKey;
861 });
862 if (doNotSign) {
863 mOkButton->setIcon(KStandardGuiItem::ok().icon());
864 mOkButton->setStyleSheet({});
865 } else {
866 DeVSCompliance::decorate(mOkButton, de_vs);
867 }
868 mComplianceLbl->setText(DeVSCompliance::name(de_vs));
869 mComplianceLbl->setVisible(!doNotSign);
870 }
871
872 GpgME::Protocol mForcedProtocol;
873 QList<KeySelectionCombo *> mSigningCombos;
876 QScrollArea *mScrollArea;
877 QVBoxLayout *mScrollLayout;
878 QPushButton *mOkButton;
879 QVBoxLayout *mMainLay;
880 QButtonGroup *mFormatBtns;
881 QString mSender;
882 bool mSign;
883 bool mEncrypt;
884 bool mAllowMixed;
886 QList<QGpgME::Job *> mRunningJobs;
887 GpgME::Error mLastError;
888 QLabel *mComplianceLbl;
889 KeyResolver::Solution mAcceptedResult;
890 QString mGenerateTooltip;
891};
892
894 bool sign,
895 const QString &sender,
896 KeyResolver::Solution preferredSolution,
897 KeyResolver::Solution alternativeSolution,
898 bool allowMixed,
899 GpgME::Protocol forcedProtocol,
900 QWidget *parent,
902 : QDialog(parent, f)
903 , d{std::make_unique<Private>(this, encrypt, sign, forcedProtocol, preferredSolution.protocol, sender, allowMixed)}
904{
905 if (sign) {
906 d->setSigningKeys(std::move(preferredSolution.signingKeys), std::move(alternativeSolution.signingKeys));
907 }
908 if (encrypt) {
909 d->setEncryptionKeys(allowMixed ? UnknownProtocol : preferredSolution.protocol,
910 std::move(preferredSolution.encryptionKeys),
911 allowMixed ? UnknownProtocol : alternativeSolution.protocol,
912 std::move(alternativeSolution.encryptionKeys));
913 }
914 d->updateWidgets();
915 d->updateOkButton();
916
917 const auto size = sizeHint();
918 const auto desk = screen()->size();
919 resize(QSize(desk.width() / 3, qMin(size.height(), desk.height() / 2)));
920}
921
922Kleo::NewKeyApprovalDialog::~NewKeyApprovalDialog() = default;
923
925{
926 return d->mAcceptedResult;
927}
928
929void NewKeyApprovalDialog::handleKeyGenResult()
930{
931 if (d->mRunningJobs.empty()) {
932 qCWarning(LIBKLEO_LOG) << __func__ << "No running job";
933 }
934 const auto job = d->mRunningJobs.front();
935 const auto result = QGpgME::Job::context(job)->keyGenerationResult();
936 const auto combo = d->findVisibleKeySelectionComboWithGenerateKey();
937 d->handleKeyGenResult(result, job, combo);
938}
939
940#include "newkeyapprovaldialog.moc"
941
942#include "moc_newkeyapprovaldialog.cpp"
This class improves a few aspects of QScrollArea for usage by us, in particular, for vertically scrol...
Default implementation of key filter class.
A dialog to show for encryption / signing key approval or selection.
NewKeyApprovalDialog(bool encrypt, bool sign, const QString &sender, KeyResolver::Solution preferredSolution, KeyResolver::Solution alternativeSolution, bool allowMixed, GpgME::Protocol forcedProtocol, QWidget *parent=nullptr, Qt::WindowFlags f=Qt::WindowFlags())
Create a new Key Approval Dialog.
KeyResolver::Solution result()
The selected signing and/or encryption keys.
A progress dialog for Kleo::Jobs.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
PostalAddress address(const QVariant &location)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
void setChecked(bool)
void clicked(bool checked)
void setIcon(const QIcon &icon)
void setSizeAdjustPolicy(SizeAdjustPolicy policy)
void addLayout(QLayout *layout, int stretch)
void addStretch(int stretch)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
void addButton(QAbstractButton *button, int id)
QAbstractButton * button(int id) const const
void buttonClicked(QAbstractButton *button)
void setExclusive(bool)
void idClicked(int id)
void currentIndexChanged(int index)
virtual void accept()
virtual void reject()
virtual QSize sizeHint() const const override
QPushButton * button(StandardButton which) const const
void setFrameStyle(int style)
void addWidget(QWidget *widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment)
int rowCount() const const
void setColumnStretch(int column, int stretch)
QIcon fromTheme(const QString &name)
void setText(const QString &)
void setContentsMargins(const QMargins &margins)
void clear()
bool empty() const const
qsizetype removeAll(const AT &t)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QList< T > findChildren(Qt::FindChildOptions options) const const
QVariant property(const char *name) const const
T qobject_cast(QObject *object)
QObject * sender() const const
void setObjectName(QAnyStringView name)
void setWidget(QWidget *widget)
QWidget * widget() const const
void setWidgetResizable(bool resizable)
QString fromLatin1(QByteArrayView str)
QString fromStdString(const std::string &str)
bool isEmpty() const const
QString toLower() const const
AlignLeft
ToolTipRole
WindowContextHelpButtonHint
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
QString toString() const const
void setEnabled(bool)
void setMinimumHeight(int minh)
QWidget * parentWidget() const const
QScreen * screen() const const
void setLayout(QLayout *layout)
void setStyleSheet(const QString &styleSheet)
virtual void setVisible(bool visible)
void setWindowTitle(const QString &)
Solution represents the solution found by the KeyResolver.
Definition keyresolver.h:65
GpgME::Protocol protocol
This property holds a hint at the protocol of the signing and encryption keys, i.e.
Definition keyresolver.h:72
QMap< QString, std::vector< GpgME::Key > > encryptionKeys
This property contains the encryption keys to use for the different recipients.
Definition keyresolver.h:91
std::vector< GpgME::Key > signingKeys
This property contains the signing keys to use.
Definition keyresolver.h:78
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 22 2024 12:00:53 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.