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 
55 using namespace Kleo;
56 using namespace GpgME;
57 
58 namespace
59 {
60 class EncryptFilter : public DefaultKeyFilter
61 {
62 public:
63  EncryptFilter()
65  {
66  setHasEncrypt(DefaultKeyFilter::Set);
67  }
68 };
69 static std::shared_ptr<KeyFilter> s_encryptFilter = std::shared_ptr<KeyFilter>(new EncryptFilter);
70 
71 class OpenPGPFilter : public DefaultKeyFilter
72 {
73 public:
74  OpenPGPFilter()
76  {
77  setIsOpenPGP(DefaultKeyFilter::Set);
78  setHasEncrypt(DefaultKeyFilter::Set);
79  }
80 };
81 static std::shared_ptr<KeyFilter> s_pgpEncryptFilter = std::shared_ptr<KeyFilter>(new OpenPGPFilter);
82 
83 class OpenPGPSignFilter : public DefaultKeyFilter
84 {
85 public:
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 };
98 static std::shared_ptr<KeyFilter> s_pgpSignFilter = std::shared_ptr<KeyFilter>(new OpenPGPSignFilter);
99 
100 class SMIMEFilter : public DefaultKeyFilter
101 {
102 public:
103  SMIMEFilter()
104  : DefaultKeyFilter()
105  {
106  setIsOpenPGP(DefaultKeyFilter::NotSet);
107  setHasEncrypt(DefaultKeyFilter::Set);
108  }
109 };
110 static std::shared_ptr<KeyFilter> s_smimeEncryptFilter = std::shared_ptr<KeyFilter>(new SMIMEFilter);
111 
112 class SMIMESignFilter : public DefaultKeyFilter
113 {
114 public:
115  SMIMESignFilter()
116  : DefaultKeyFilter()
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 };
126 static 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 */
129 class ComboWidget : public QWidget
130 {
131  Q_OBJECT
132 public:
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(i18n("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(i18n("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 
208 private:
209  KeySelectionCombo *mCombo;
210  QPushButton *mFilterBtn;
211  QString mLastIdFilter;
212  GpgME::Protocol mFixedProtocol = GpgME::UnknownProtocol;
213 };
214 
215 static 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 
225 Key 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 
235 class NewKeyApprovalDialog::Private
236 {
237 private:
238  enum Action {
239  Unset,
240  GenerateKey,
241  IgnoreKey,
242  };
243 
244 public:
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 E-Mail address.<br/><br/>"
273  "The key is necessary to decrypt and sign E-Mails. "
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);
293  mScrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents);
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(i18n("OpenPGP"));
305  smimeBtn = new QCheckBox(i18n("S/MIME"));
306  } else {
307  pgpBtn = new QRadioButton(i18n("OpenPGP"));
308  smimeBtn = new QRadioButton(i18n("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), i18n("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("Don't confirm identity and integrity"),
558  IgnoreKey,
559  i18nc("@info:tooltip for not selecting a key for signing.", "The E-Mail 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 E-Mail, 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  DeVSCompliance::decorate(mOkButton, de_vs);
860  mComplianceLbl->setText(DeVSCompliance::name(de_vs));
861  mComplianceLbl->setVisible(true);
862  }
863 
864  GpgME::Protocol mForcedProtocol;
865  QList<KeySelectionCombo *> mSigningCombos;
866  QList<KeySelectionCombo *> mEncCombos;
867  QList<KeySelectionCombo *> mAllCombos;
868  QScrollArea *mScrollArea;
869  QVBoxLayout *mScrollLayout;
870  QPushButton *mOkButton;
871  QVBoxLayout *mMainLay;
872  QButtonGroup *mFormatBtns;
873  QString mSender;
874  bool mSign;
875  bool mEncrypt;
876  bool mAllowMixed;
878  QList<QGpgME::Job *> mRunningJobs;
879  GpgME::Error mLastError;
880  QLabel *mComplianceLbl;
881  KeyResolver::Solution mAcceptedResult;
882  QString mGenerateTooltip;
883 };
884 
886  bool sign,
887  const QString &sender,
888  KeyResolver::Solution preferredSolution,
889  KeyResolver::Solution alternativeSolution,
890  bool allowMixed,
891  GpgME::Protocol forcedProtocol,
892  QWidget *parent,
893  Qt::WindowFlags f)
894  : QDialog(parent, f)
895  , d{std::make_unique<Private>(this, encrypt, sign, forcedProtocol, preferredSolution.protocol, sender, allowMixed)}
896 {
897  if (sign) {
898  d->setSigningKeys(std::move(preferredSolution.signingKeys), std::move(alternativeSolution.signingKeys));
899  }
900  if (encrypt) {
901  d->setEncryptionKeys(allowMixed ? UnknownProtocol : preferredSolution.protocol,
902  std::move(preferredSolution.encryptionKeys),
903  allowMixed ? UnknownProtocol : alternativeSolution.protocol,
904  std::move(alternativeSolution.encryptionKeys));
905  }
906  d->updateWidgets();
907  d->updateOkButton();
908 
909  const auto size = sizeHint();
910  const auto desk = screen()->size();
911  resize(QSize(desk.width() / 3, qMin(size.height(), desk.height() / 2)));
912 }
913 
914 Kleo::NewKeyApprovalDialog::~NewKeyApprovalDialog() = default;
915 
917 {
918  return d->mAcceptedResult;
919 }
920 
921 void NewKeyApprovalDialog::handleKeyGenResult()
922 {
923  if (d->mRunningJobs.empty()) {
924  qCWarning(LIBKLEO_LOG) << __func__ << "No running job";
925  }
926  const auto job = d->mRunningJobs.front();
927  const auto result = QGpgME::Job::context(job)->keyGenerationResult();
928  const auto combo = d->findVisibleKeySelectionComboWithGenerateKey();
929  d->handleKeyGenResult(result, job, combo);
930 }
931 
932 #include "newkeyapprovaldialog.moc"
933 
934 #include "moc_newkeyapprovaldialog.cpp"
void showText(const QPoint &pos, const QString &text, QWidget *w)
AlignLeft
ToolTipRole
virtual void reject()
KeyResolver::Solution result()
The selected signing and/or encryption keys.
Default implementation of key filter class.
GpgME::Protocol protocol
This property holds a hint at the protocol of the signing and encryption keys, i.e.
Definition: keyresolver.h:72
void buttonClicked(QAbstractButton *button)
void clicked(bool checked)
QIcon fromTheme(const QString &name)
QObject * sender() const const
void setChecked(bool)
This class improves a few aspects of QScrollArea for usage by us, in particular, for vertically scrol...
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
virtual QWidget * widget()
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
virtual void setVisible(bool visible)
WindowContextHelpButtonHint
void setColumnStretch(int column, int stretch)
QString i18n(const char *text, const TYPE &arg...)
QString fromStdString(const std::string &str)
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.
bool isEmpty() const const
A dialog to show for encryption / signing key approval or selection.
void push_back(QChar ch)
int rowCount() const const
PostalAddress address(const QVariant &location)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
void setIcon(const QIcon &icon)
QString toLower() const const
QPushButton * button(QDialogButtonBox::StandardButton which) const const
A progress dialog for Kleo::Jobs.
QString fromLatin1(const char *str, int size)
void setObjectName(const QString &name)
void currentIndexChanged(int index)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
void addWidget(QWidget *widget, int row, int column, Qt::Alignment alignment)
void setLayout(QLayout *layout)
Solution represents the solution found by the KeyResolver.
Definition: keyresolver.h:65
void setWidget(QWidget *widget)
void idClicked(int id)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Thu Feb 15 2024 03:56:14 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.