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

KDE's Doxygen guidelines are available online.