File indexing completed on 2025-01-05 04:55:50

0001 /*  -*- c++ -*-
0002     newkeyapprovaldialog.cpp
0003 
0004     This file is part of libkleopatra, the KDE keymanagement library
0005     SPDX-FileCopyrightText: 2018 Intevation GmbH
0006     SPDX-FileCopyrightText: 2021 g10 Code GmbH
0007     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include <config-libkleo.h>
0013 
0014 #include "newkeyapprovaldialog.h"
0015 
0016 #include "keyselectioncombo.h"
0017 #include "progressdialog.h"
0018 
0019 #include <libkleo/adjustingscrollarea.h>
0020 #include <libkleo/algorithm.h>
0021 #include <libkleo/compliance.h>
0022 #include <libkleo/debug.h>
0023 #include <libkleo/defaultkeyfilter.h>
0024 #include <libkleo/formatting.h>
0025 #include <libkleo/gnupg.h>
0026 #include <libkleo/keyhelpers.h>
0027 #include <libkleo/systeminfo.h>
0028 
0029 #include <libkleo_debug.h>
0030 
0031 #include <KColorScheme>
0032 #include <KLocalizedString>
0033 #include <KMessageBox>
0034 
0035 #include <QGpgME/Protocol>
0036 #include <QGpgME/QuickJob>
0037 
0038 #include <QButtonGroup>
0039 #include <QCheckBox>
0040 #include <QDialogButtonBox>
0041 #include <QGroupBox>
0042 #include <QHBoxLayout>
0043 #include <QLabel>
0044 #include <QMap>
0045 #include <QPushButton>
0046 #include <QRadioButton>
0047 #include <QScreen>
0048 #include <QToolTip>
0049 #include <QVBoxLayout>
0050 
0051 #include <gpgme++/context.h>
0052 #include <gpgme++/key.h>
0053 #include <gpgme++/keygenerationresult.h>
0054 
0055 using namespace Kleo;
0056 using namespace GpgME;
0057 
0058 namespace
0059 {
0060 class EncryptFilter : public DefaultKeyFilter
0061 {
0062 public:
0063     EncryptFilter()
0064         : DefaultKeyFilter()
0065     {
0066         setHasEncrypt(DefaultKeyFilter::Set);
0067     }
0068 };
0069 static std::shared_ptr<KeyFilter> s_encryptFilter = std::shared_ptr<KeyFilter>(new EncryptFilter);
0070 
0071 class OpenPGPFilter : public DefaultKeyFilter
0072 {
0073 public:
0074     OpenPGPFilter()
0075         : DefaultKeyFilter()
0076     {
0077         setIsOpenPGP(DefaultKeyFilter::Set);
0078         setHasEncrypt(DefaultKeyFilter::Set);
0079     }
0080 };
0081 static std::shared_ptr<KeyFilter> s_pgpEncryptFilter = std::shared_ptr<KeyFilter>(new OpenPGPFilter);
0082 
0083 class OpenPGPSignFilter : public DefaultKeyFilter
0084 {
0085 public:
0086     OpenPGPSignFilter()
0087         : DefaultKeyFilter()
0088     {
0089         /* Also list unusable keys to make it transparent why they are unusable */
0090         setDisabled(DefaultKeyFilter::NotSet);
0091         setRevoked(DefaultKeyFilter::NotSet);
0092         setExpired(DefaultKeyFilter::NotSet);
0093         setCanSign(DefaultKeyFilter::Set);
0094         setHasSecret(DefaultKeyFilter::Set);
0095         setIsOpenPGP(DefaultKeyFilter::Set);
0096     }
0097 };
0098 static std::shared_ptr<KeyFilter> s_pgpSignFilter = std::shared_ptr<KeyFilter>(new OpenPGPSignFilter);
0099 
0100 class SMIMEFilter : public DefaultKeyFilter
0101 {
0102 public:
0103     SMIMEFilter()
0104         : DefaultKeyFilter()
0105     {
0106         setIsOpenPGP(DefaultKeyFilter::NotSet);
0107         setHasEncrypt(DefaultKeyFilter::Set);
0108     }
0109 };
0110 static std::shared_ptr<KeyFilter> s_smimeEncryptFilter = std::shared_ptr<KeyFilter>(new SMIMEFilter);
0111 
0112 class SMIMESignFilter : public DefaultKeyFilter
0113 {
0114 public:
0115     SMIMESignFilter()
0116         : DefaultKeyFilter()
0117     {
0118         setDisabled(DefaultKeyFilter::NotSet);
0119         setRevoked(DefaultKeyFilter::NotSet);
0120         setExpired(DefaultKeyFilter::NotSet);
0121         setCanSign(DefaultKeyFilter::Set);
0122         setIsOpenPGP(DefaultKeyFilter::NotSet);
0123         setHasSecret(DefaultKeyFilter::Set);
0124     }
0125 };
0126 static std::shared_ptr<KeyFilter> s_smimeSignFilter = std::shared_ptr<KeyFilter>(new SMIMESignFilter);
0127 
0128 /* Some decoration and a button to remove the filter for a keyselectioncombo */
0129 class ComboWidget : public QWidget
0130 {
0131     Q_OBJECT
0132 public:
0133     explicit ComboWidget(KeySelectionCombo *combo)
0134         : mCombo(combo)
0135         , mFilterBtn(new QPushButton)
0136     {
0137         auto hLay = new QHBoxLayout(this);
0138         auto infoBtn = new QPushButton;
0139         infoBtn->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual")));
0140         infoBtn->setIconSize(QSize(22, 22));
0141         infoBtn->setFlat(true);
0142         infoBtn->setAccessibleName(i18nc("@action:button", "Show Details"));
0143         hLay->addWidget(infoBtn);
0144         hLay->addWidget(combo, 1);
0145         hLay->addWidget(mFilterBtn, 0);
0146 
0147         connect(infoBtn, &QPushButton::clicked, this, [this, infoBtn]() {
0148             QToolTip::showText(infoBtn->mapToGlobal(QPoint()) + QPoint(infoBtn->width(), 0),
0149                                mCombo->currentData(Qt::ToolTipRole).toString(),
0150                                infoBtn,
0151                                QRect(),
0152                                30000);
0153         });
0154 
0155         // FIXME: This is ugly to enforce but otherwise the
0156         // icon is broken.
0157         combo->setMinimumHeight(22);
0158         mFilterBtn->setMinimumHeight(23);
0159 
0160         updateFilterButton();
0161 
0162         connect(mFilterBtn, &QPushButton::clicked, this, [this]() {
0163             const QString curFilter = mCombo->idFilter();
0164             if (curFilter.isEmpty()) {
0165                 setIdFilter(mLastIdFilter);
0166                 mLastIdFilter = QString();
0167             } else {
0168                 setIdFilter(QString());
0169                 mLastIdFilter = curFilter;
0170             }
0171         });
0172     }
0173 
0174     void setIdFilter(const QString &id)
0175     {
0176         mCombo->setIdFilter(id);
0177         updateFilterButton();
0178     }
0179 
0180     void updateFilterButton()
0181     {
0182         if (mCombo->idFilter().isEmpty()) {
0183             mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-add-filters")));
0184             mFilterBtn->setAccessibleName(i18nc("@action:button", "Show Matching Keys"));
0185             mFilterBtn->setToolTip(i18n("Show keys matching the email address"));
0186         } else {
0187             mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters")));
0188             mFilterBtn->setAccessibleName(i18nc("@action:button short for 'Show all keys'", "Show All"));
0189             mFilterBtn->setToolTip(i18n("Show all keys"));
0190         }
0191     }
0192 
0193     KeySelectionCombo *combo()
0194     {
0195         return mCombo;
0196     }
0197 
0198     GpgME::Protocol fixedProtocol() const
0199     {
0200         return mFixedProtocol;
0201     }
0202 
0203     void setFixedProtocol(GpgME::Protocol proto)
0204     {
0205         mFixedProtocol = proto;
0206     }
0207 
0208 private:
0209     KeySelectionCombo *mCombo;
0210     QPushButton *mFilterBtn;
0211     QString mLastIdFilter;
0212     GpgME::Protocol mFixedProtocol = GpgME::UnknownProtocol;
0213 };
0214 
0215 static bool key_has_addr(const GpgME::Key &key, const QString &addr)
0216 {
0217     for (const auto &uid : key.userIDs()) {
0218         if (QString::fromStdString(uid.addrSpec()).toLower() == addr.toLower()) {
0219             return true;
0220         }
0221     }
0222     return false;
0223 }
0224 
0225 Key findfirstKeyOfType(const std::vector<Key> &keys, GpgME::Protocol protocol)
0226 {
0227     const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol](const auto &key) {
0228         return key.protocol() == protocol;
0229     });
0230     return it != std::end(keys) ? *it : Key();
0231 }
0232 
0233 } // namespace
0234 
0235 class NewKeyApprovalDialog::Private
0236 {
0237 private:
0238     enum Action {
0239         Unset,
0240         GenerateKey,
0241         IgnoreKey,
0242     };
0243 
0244 public:
0245     enum {
0246         OpenPGPButtonId = 1,
0247         SMIMEButtonId = 2,
0248     };
0249 
0250     Private(NewKeyApprovalDialog *qq,
0251             bool encrypt,
0252             bool sign,
0253             GpgME::Protocol forcedProtocol,
0254             GpgME::Protocol presetProtocol,
0255             const QString &sender,
0256             bool allowMixed)
0257         : mForcedProtocol{forcedProtocol}
0258         , mSender{sender}
0259         , mSign{sign}
0260         , mEncrypt{encrypt}
0261         , mAllowMixed{allowMixed}
0262         , q{qq}
0263     {
0264         Q_ASSERT(forcedProtocol == GpgME::UnknownProtocol || presetProtocol == GpgME::UnknownProtocol || presetProtocol == forcedProtocol);
0265         Q_ASSERT(!allowMixed || (allowMixed && forcedProtocol == GpgME::UnknownProtocol));
0266         Q_ASSERT(!(!allowMixed && presetProtocol == GpgME::UnknownProtocol));
0267 
0268         // We do the translation here to avoid having the same string multiple times.
0269         mGenerateTooltip = i18nc(
0270             "@info:tooltip for a 'Generate new key pair' action "
0271             "in a combobox when a user does not yet have an OpenPGP or S/MIME key.",
0272             "Generate a new key using your E-Mail address.<br/><br/>"
0273             "The key is necessary to decrypt and sign E-Mails. "
0274             "You will be asked for a passphrase to protect this key and the protected key "
0275             "will be stored in your home directory.");
0276         mMainLay = new QVBoxLayout;
0277 
0278         QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0279         mOkButton = btnBox->button(QDialogButtonBox::Ok);
0280 #ifndef NDEBUG
0281         mOkButton->setObjectName(QLatin1StringView("ok button"));
0282 #endif
0283         QObject::connect(btnBox, &QDialogButtonBox::accepted, q, [this]() {
0284             accepted();
0285         });
0286         QObject::connect(btnBox, &QDialogButtonBox::rejected, q, &QDialog::reject);
0287 
0288         mScrollArea = new AdjustingScrollArea;
0289         mScrollArea->setWidget(new QWidget);
0290         mScrollLayout = new QVBoxLayout;
0291         mScrollArea->widget()->setLayout(mScrollLayout);
0292         mScrollArea->setWidgetResizable(true);
0293         mScrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents);
0294         mScrollArea->setFrameStyle(QFrame::NoFrame);
0295         mScrollLayout->setContentsMargins(0, 0, 0, 0);
0296 
0297         q->setWindowTitle(i18nc("@title:window", "Security approval"));
0298 
0299         auto fmtLayout = new QHBoxLayout;
0300         mFormatBtns = new QButtonGroup(qq);
0301         QAbstractButton *pgpBtn;
0302         QAbstractButton *smimeBtn;
0303         if (mAllowMixed) {
0304             pgpBtn = new QCheckBox(i18n("OpenPGP"));
0305             smimeBtn = new QCheckBox(i18n("S/MIME"));
0306         } else {
0307             pgpBtn = new QRadioButton(i18n("OpenPGP"));
0308             smimeBtn = new QRadioButton(i18n("S/MIME"));
0309         }
0310 #ifndef NDEBUG
0311         pgpBtn->setObjectName(QLatin1StringView("openpgp button"));
0312         smimeBtn->setObjectName(QLatin1StringView("smime button"));
0313 #endif
0314         mFormatBtns->addButton(pgpBtn, OpenPGPButtonId);
0315         mFormatBtns->addButton(smimeBtn, SMIMEButtonId);
0316         mFormatBtns->setExclusive(!mAllowMixed);
0317 
0318         connect(mFormatBtns, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked), q, [this]() {
0319             updateOkButton();
0320         });
0321 
0322         fmtLayout->addStretch(-1);
0323         fmtLayout->addWidget(pgpBtn);
0324         fmtLayout->addWidget(smimeBtn);
0325         mMainLay->addLayout(fmtLayout);
0326 
0327         if (mForcedProtocol != GpgME::UnknownProtocol) {
0328             pgpBtn->setChecked(mForcedProtocol == GpgME::OpenPGP);
0329             smimeBtn->setChecked(mForcedProtocol == GpgME::CMS);
0330             pgpBtn->setVisible(false);
0331             smimeBtn->setVisible(false);
0332         } else {
0333             pgpBtn->setChecked(presetProtocol == GpgME::OpenPGP || presetProtocol == GpgME::UnknownProtocol);
0334             smimeBtn->setChecked(presetProtocol == GpgME::CMS || presetProtocol == GpgME::UnknownProtocol);
0335         }
0336 
0337         QObject::connect(mFormatBtns, &QButtonGroup::idClicked, q, [this](int buttonId) {
0338             // ensure that at least one protocol button is checked
0339             if (mAllowMixed && !mFormatBtns->button(OpenPGPButtonId)->isChecked() && !mFormatBtns->button(SMIMEButtonId)->isChecked()) {
0340                 mFormatBtns->button(buttonId == OpenPGPButtonId ? SMIMEButtonId : OpenPGPButtonId)->setChecked(true);
0341             }
0342             updateWidgets();
0343         });
0344 
0345         mMainLay->addWidget(mScrollArea);
0346 
0347         mComplianceLbl = new QLabel;
0348         mComplianceLbl->setVisible(false);
0349 #ifndef NDEBUG
0350         mComplianceLbl->setObjectName(QLatin1StringView("compliance label"));
0351 #endif
0352 
0353         auto btnLayout = new QHBoxLayout;
0354         btnLayout->addWidget(mComplianceLbl);
0355         btnLayout->addWidget(btnBox);
0356         mMainLay->addLayout(btnLayout);
0357 
0358         q->setLayout(mMainLay);
0359     }
0360 
0361     ~Private() = default;
0362 
0363     GpgME::Protocol currentProtocol()
0364     {
0365         const bool openPGPButtonChecked = mFormatBtns->button(OpenPGPButtonId)->isChecked();
0366         const bool smimeButtonChecked = mFormatBtns->button(SMIMEButtonId)->isChecked();
0367         if (mAllowMixed) {
0368             if (openPGPButtonChecked && !smimeButtonChecked) {
0369                 return GpgME::OpenPGP;
0370             }
0371             if (!openPGPButtonChecked && smimeButtonChecked) {
0372                 return GpgME::CMS;
0373             }
0374         } else if (openPGPButtonChecked) {
0375             return GpgME::OpenPGP;
0376         } else if (smimeButtonChecked) {
0377             return GpgME::CMS;
0378         }
0379         return GpgME::UnknownProtocol;
0380     }
0381 
0382     auto findVisibleKeySelectionComboWithGenerateKey()
0383     {
0384         const auto it = std::find_if(std::begin(mAllCombos), std::end(mAllCombos), [](auto combo) {
0385             return combo->isVisible() && combo->currentData(Qt::UserRole).toInt() == GenerateKey;
0386         });
0387         return it != std::end(mAllCombos) ? *it : nullptr;
0388     }
0389 
0390     void generateKey(KeySelectionCombo *combo)
0391     {
0392         if (!mRunningJobs.empty()) {
0393             return;
0394         }
0395         const auto &addr = combo->property("address").toString();
0396         auto job = QGpgME::openpgp()->quickJob();
0397         auto progress =
0398             new Kleo::ProgressDialog(job, i18n("Generating key for '%1'...", addr) + QStringLiteral("\n\n") + i18n("This can take several minutes."), q);
0399         progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowContextHelpButtonHint);
0400         progress->setWindowTitle(i18nc("@title:window", "Key generation"));
0401         progress->setModal(true);
0402         progress->setAutoClose(true);
0403         progress->setMinimumDuration(0);
0404         progress->setValue(0);
0405 
0406         mRunningJobs << job;
0407         if (!connect(job, &QGpgME::QuickJob::result, q, [this, job, combo]() {
0408                 handleKeyGenResult(QGpgME::Job::context(job)->keyGenerationResult(), job, combo);
0409             })) {
0410             qCWarning(LIBKLEO_LOG) << "new-style connect failed; connecting to QGpgME::QuickJob::result the old way";
0411             connect(job, SIGNAL(result(const GpgME::Error &)), q, SLOT(handleKeyGenResult()));
0412         }
0413         job->startCreate(addr, nullptr);
0414         return;
0415     }
0416 
0417     void handleKeyGenResult(const GpgME::KeyGenerationResult &result, QGpgME::Job *job, KeySelectionCombo *combo)
0418     {
0419         mLastError = result.error();
0420         if (!mLastError) {
0421             connect(combo, &KeySelectionCombo::keyListingFinished, q, [this, job]() {
0422                 mRunningJobs.removeAll(job);
0423             });
0424             // update all combos showing the GenerateKey item
0425             for (auto c : std::as_const(mAllCombos)) {
0426                 if (c->currentData(Qt::UserRole).toInt() == GenerateKey) {
0427                     c->setDefaultKey(QString::fromLatin1(result.fingerprint()), GpgME::OpenPGP);
0428                     c->refreshKeys();
0429                 }
0430             }
0431         } else {
0432             mRunningJobs.removeAll(job);
0433         }
0434     }
0435 
0436     void checkAccepted()
0437     {
0438         if (mLastError) {
0439             KMessageBox::error(q, Formatting::errorAsString(mLastError), i18n("Operation Failed"));
0440             mRunningJobs.clear();
0441             return;
0442         }
0443 
0444         if (!mRunningJobs.empty()) {
0445             return;
0446         }
0447 
0448         /* Save the keys */
0449         mAcceptedResult.protocol = currentProtocol();
0450         for (const auto combo : std::as_const(mEncCombos)) {
0451             const auto addr = combo->property("address").toString();
0452             const auto key = combo->currentKey();
0453             if (!combo->isVisible() || key.isNull()) {
0454                 continue;
0455             }
0456             mAcceptedResult.encryptionKeys[addr].push_back(key);
0457         }
0458         for (const auto combo : std::as_const(mSigningCombos)) {
0459             const auto key = combo->currentKey();
0460             if (!combo->isVisible() || key.isNull()) {
0461                 continue;
0462             }
0463             mAcceptedResult.signingKeys.push_back(key);
0464         }
0465 
0466         q->accept();
0467     }
0468 
0469     void accepted()
0470     {
0471         // We can assume everything was validly resolved, otherwise
0472         // the OK button would have been disabled.
0473         // Handle custom items now.
0474         if (auto combo = findVisibleKeySelectionComboWithGenerateKey()) {
0475             generateKey(combo);
0476             return;
0477         }
0478         checkAccepted();
0479     }
0480 
0481     auto encryptionKeyFilter(GpgME::Protocol protocol)
0482     {
0483         switch (protocol) {
0484         case OpenPGP:
0485             return s_pgpEncryptFilter;
0486         case CMS:
0487             return s_smimeEncryptFilter;
0488         default:
0489             return s_encryptFilter;
0490         }
0491     }
0492 
0493     void updateWidgets()
0494     {
0495         const GpgME::Protocol protocol = currentProtocol();
0496         const auto encryptionFilter = encryptionKeyFilter(protocol);
0497 
0498         for (auto combo : std::as_const(mSigningCombos)) {
0499             auto widget = qobject_cast<ComboWidget *>(combo->parentWidget());
0500             if (!widget) {
0501                 qCDebug(LIBKLEO_LOG) << "Failed to find signature combo widget";
0502                 continue;
0503             }
0504             widget->setVisible(protocol == GpgME::UnknownProtocol || widget->fixedProtocol() == GpgME::UnknownProtocol || widget->fixedProtocol() == protocol);
0505         }
0506         for (auto combo : std::as_const(mEncCombos)) {
0507             auto widget = qobject_cast<ComboWidget *>(combo->parentWidget());
0508             if (!widget) {
0509                 qCDebug(LIBKLEO_LOG) << "Failed to find combo widget";
0510                 continue;
0511             }
0512             widget->setVisible(protocol == GpgME::UnknownProtocol || widget->fixedProtocol() == GpgME::UnknownProtocol || widget->fixedProtocol() == protocol);
0513             if (widget->isVisible() && combo->property("address") != mSender) {
0514                 combo->setKeyFilter(encryptionFilter);
0515             }
0516         }
0517         // hide the labels indicating the protocol of the sender's keys if only a single protocol is active
0518         const auto protocolLabels = q->findChildren<QLabel *>(QStringLiteral("protocol label"));
0519         for (auto label : protocolLabels) {
0520             label->setVisible(protocol == GpgME::UnknownProtocol);
0521         }
0522     }
0523 
0524     auto createProtocolLabel(GpgME::Protocol protocol)
0525     {
0526         auto label = new QLabel(Formatting::displayName(protocol));
0527         label->setObjectName(QLatin1StringView("protocol label"));
0528         return label;
0529     }
0530 
0531     ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key, GpgME::Protocol protocol = GpgME::UnknownProtocol)
0532     {
0533         Q_ASSERT(!key.isNull() || protocol != UnknownProtocol);
0534         protocol = !key.isNull() ? key.protocol() : protocol;
0535 
0536         auto combo = new KeySelectionCombo{true, KeyUsage::Sign};
0537         auto comboWidget = new ComboWidget(combo);
0538 #ifndef NDEBUG
0539         combo->setObjectName(QLatin1StringView("signing key"));
0540 #endif
0541         if (protocol == GpgME::OpenPGP) {
0542             combo->setKeyFilter(s_pgpSignFilter);
0543         } else if (protocol == GpgME::CMS) {
0544             combo->setKeyFilter(s_smimeSignFilter);
0545         }
0546         if (key.isNull() || key_has_addr(key, mSender)) {
0547             comboWidget->setIdFilter(mSender);
0548         }
0549         comboWidget->setFixedProtocol(protocol);
0550         if (!key.isNull()) {
0551             combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), protocol);
0552         }
0553         if (key.isNull() && protocol == OpenPGP) {
0554             combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey, mGenerateTooltip);
0555         }
0556         combo->appendCustomItem(Formatting::unavailableIcon(),
0557                                 i18n("Don't confirm identity and integrity"),
0558                                 IgnoreKey,
0559                                 i18nc("@info:tooltip for not selecting a key for signing.", "The E-Mail will not be cryptographically signed."));
0560 
0561         mSigningCombos << combo;
0562         mAllCombos << combo;
0563         combo->setProperty("address", addr);
0564 
0565         connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this]() {
0566             updateOkButton();
0567         });
0568         connect(combo, qOverload<int>(&QComboBox::currentIndexChanged), q, [this]() {
0569             updateOkButton();
0570         });
0571 
0572         return comboWidget;
0573     }
0574 
0575     void setSigningKeys(const std::vector<GpgME::Key> &preferredKeys, const std::vector<GpgME::Key> &alternativeKeys)
0576     {
0577         auto group = new QGroupBox(i18nc("Caption for signing key selection", "Confirm identity '%1' as:", mSender));
0578         group->setAlignment(Qt::AlignLeft);
0579         auto sigLayout = new QVBoxLayout(group);
0580 
0581         const bool mayNeedOpenPGP = mForcedProtocol != CMS;
0582         const bool mayNeedCMS = mForcedProtocol != OpenPGP;
0583         if (mayNeedOpenPGP) {
0584             if (mAllowMixed) {
0585                 sigLayout->addWidget(createProtocolLabel(OpenPGP));
0586             }
0587             const Key preferredKey = findfirstKeyOfType(preferredKeys, OpenPGP);
0588             const Key alternativeKey = findfirstKeyOfType(alternativeKeys, OpenPGP);
0589             if (!preferredKey.isNull()) {
0590                 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey;
0591                 auto comboWidget = createSigningCombo(mSender, preferredKey);
0592                 sigLayout->addWidget(comboWidget);
0593             } else if (!alternativeKey.isNull()) {
0594                 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey;
0595                 auto comboWidget = createSigningCombo(mSender, alternativeKey);
0596                 sigLayout->addWidget(comboWidget);
0597             } else {
0598                 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for OpenPGP key";
0599                 auto comboWidget = createSigningCombo(mSender, Key(), OpenPGP);
0600                 sigLayout->addWidget(comboWidget);
0601             }
0602         }
0603         if (mayNeedCMS) {
0604             if (mAllowMixed) {
0605                 sigLayout->addWidget(createProtocolLabel(CMS));
0606             }
0607             const Key preferredKey = findfirstKeyOfType(preferredKeys, CMS);
0608             const Key alternativeKey = findfirstKeyOfType(alternativeKeys, CMS);
0609             if (!preferredKey.isNull()) {
0610                 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey;
0611                 auto comboWidget = createSigningCombo(mSender, preferredKey);
0612                 sigLayout->addWidget(comboWidget);
0613             } else if (!alternativeKey.isNull()) {
0614                 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey;
0615                 auto comboWidget = createSigningCombo(mSender, alternativeKey);
0616                 sigLayout->addWidget(comboWidget);
0617             } else {
0618                 qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for S/MIME key";
0619                 auto comboWidget = createSigningCombo(mSender, Key(), CMS);
0620                 sigLayout->addWidget(comboWidget);
0621             }
0622         }
0623 
0624         mScrollLayout->addWidget(group);
0625     }
0626 
0627     ComboWidget *createEncryptionCombo(const QString &addr, const GpgME::Key &key, GpgME::Protocol fixedProtocol)
0628     {
0629         auto combo = new KeySelectionCombo{false, KeyUsage::Encrypt};
0630         auto comboWidget = new ComboWidget(combo);
0631 #ifndef NDEBUG
0632         combo->setObjectName(QLatin1StringView("encryption key"));
0633 #endif
0634         if (fixedProtocol == GpgME::OpenPGP) {
0635             combo->setKeyFilter(s_pgpEncryptFilter);
0636         } else if (fixedProtocol == GpgME::CMS) {
0637             combo->setKeyFilter(s_smimeEncryptFilter);
0638         } else {
0639             combo->setKeyFilter(s_encryptFilter);
0640         }
0641         if (key.isNull() || key_has_addr(key, addr)) {
0642             comboWidget->setIdFilter(addr);
0643         }
0644         comboWidget->setFixedProtocol(fixedProtocol);
0645         if (!key.isNull()) {
0646             combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), fixedProtocol);
0647         }
0648 
0649         if (addr == mSender && key.isNull() && fixedProtocol == OpenPGP) {
0650             combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey, mGenerateTooltip);
0651         }
0652 
0653         combo->appendCustomItem(Formatting::unavailableIcon(),
0654                                 i18n("No key. Recipient will be unable to decrypt."),
0655                                 IgnoreKey,
0656                                 i18nc("@info:tooltip for No Key selected for a specific recipient.",
0657                                       "Do not select a key for this recipient.<br/><br/>"
0658                                       "The recipient will receive the encrypted E-Mail, but it can only "
0659                                       "be decrypted with the other keys selected in this dialog."));
0660 
0661         connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this]() {
0662             updateOkButton();
0663         });
0664         connect(combo, qOverload<int>(&QComboBox::currentIndexChanged), q, [this]() {
0665             updateOkButton();
0666         });
0667 
0668         mEncCombos << combo;
0669         mAllCombos << combo;
0670         combo->setProperty("address", addr);
0671         return comboWidget;
0672     }
0673 
0674     void addEncryptionAddr(const QString &addr,
0675                            GpgME::Protocol preferredKeysProtocol,
0676                            const std::vector<GpgME::Key> &preferredKeys,
0677                            GpgME::Protocol alternativeKeysProtocol,
0678                            const std::vector<GpgME::Key> &alternativeKeys,
0679                            QGridLayout *encGrid)
0680     {
0681         if (addr == mSender) {
0682             const bool mayNeedOpenPGP = mForcedProtocol != CMS;
0683             const bool mayNeedCMS = mForcedProtocol != OpenPGP;
0684             if (mayNeedOpenPGP) {
0685                 if (mAllowMixed) {
0686                     encGrid->addWidget(createProtocolLabel(OpenPGP), encGrid->rowCount(), 0);
0687                 }
0688                 for (const auto &key : preferredKeys) {
0689                     if (key.protocol() == OpenPGP) {
0690                         qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
0691                         auto comboWidget = createEncryptionCombo(addr, key, OpenPGP);
0692                         encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
0693                     }
0694                 }
0695                 for (const auto &key : alternativeKeys) {
0696                     if (key.protocol() == OpenPGP) {
0697                         qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
0698                         auto comboWidget = createEncryptionCombo(addr, key, OpenPGP);
0699                         encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
0700                     }
0701                 }
0702                 if (!anyKeyHasProtocol(preferredKeys, OpenPGP) && !anyKeyHasProtocol(alternativeKeys, OpenPGP)) {
0703                     qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for OpenPGP key";
0704                     auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), OpenPGP);
0705                     encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
0706                 }
0707             }
0708             if (mayNeedCMS) {
0709                 if (mAllowMixed) {
0710                     encGrid->addWidget(createProtocolLabel(CMS), encGrid->rowCount(), 0);
0711                 }
0712                 for (const auto &key : preferredKeys) {
0713                     if (key.protocol() == CMS) {
0714                         qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
0715                         auto comboWidget = createEncryptionCombo(addr, key, CMS);
0716                         encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
0717                     }
0718                 }
0719                 for (const auto &key : alternativeKeys) {
0720                     if (key.protocol() == CMS) {
0721                         qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
0722                         auto comboWidget = createEncryptionCombo(addr, key, CMS);
0723                         encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
0724                     }
0725                 }
0726                 if (!anyKeyHasProtocol(preferredKeys, CMS) && !anyKeyHasProtocol(alternativeKeys, CMS)) {
0727                     qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for S/MIME key";
0728                     auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), CMS);
0729                     encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
0730                 }
0731             }
0732         } else {
0733             encGrid->addWidget(new QLabel(addr), encGrid->rowCount(), 0);
0734 
0735             for (const auto &key : preferredKeys) {
0736                 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
0737                 auto comboWidget = createEncryptionCombo(addr, key, preferredKeysProtocol);
0738                 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
0739             }
0740             for (const auto &key : alternativeKeys) {
0741                 qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
0742                 auto comboWidget = createEncryptionCombo(addr, key, alternativeKeysProtocol);
0743                 encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
0744             }
0745             if (!mAllowMixed) {
0746                 if (preferredKeys.empty()) {
0747                     qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(preferredKeysProtocol)
0748                                          << "key";
0749                     auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), preferredKeysProtocol);
0750                     encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
0751                 }
0752                 if (alternativeKeys.empty() && alternativeKeysProtocol != UnknownProtocol) {
0753                     qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for"
0754                                          << Formatting::displayName(alternativeKeysProtocol) << "key";
0755                     auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), alternativeKeysProtocol);
0756                     encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
0757                 }
0758             } else {
0759                 if (preferredKeys.empty() && alternativeKeys.empty()) {
0760                     qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for any key";
0761                     auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), UnknownProtocol);
0762                     encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
0763                 }
0764             }
0765         }
0766     }
0767 
0768     void setEncryptionKeys(GpgME::Protocol preferredKeysProtocol,
0769                            const QMap<QString, std::vector<GpgME::Key>> &preferredKeys,
0770                            GpgME::Protocol alternativeKeysProtocol,
0771                            const QMap<QString, std::vector<GpgME::Key>> &alternativeKeys)
0772     {
0773         {
0774             auto group = new QGroupBox(i18nc("Encrypt to self (email address):", "Encrypt to self (%1):", mSender));
0775 #ifndef NDEBUG
0776             group->setObjectName(QLatin1StringView("encrypt-to-self box"));
0777 #endif
0778             group->setAlignment(Qt::AlignLeft);
0779             auto encGrid = new QGridLayout(group);
0780 
0781             addEncryptionAddr(mSender, preferredKeysProtocol, preferredKeys.value(mSender), alternativeKeysProtocol, alternativeKeys.value(mSender), encGrid);
0782 
0783             encGrid->setColumnStretch(1, -1);
0784             mScrollLayout->addWidget(group);
0785         }
0786 
0787         const bool hasOtherRecipients = std::any_of(preferredKeys.keyBegin(), preferredKeys.keyEnd(), [this](const auto &recipient) {
0788             return recipient != mSender;
0789         });
0790         if (hasOtherRecipients) {
0791             auto group = new QGroupBox(i18n("Encrypt to others:"));
0792 #ifndef NDEBUG
0793             group->setObjectName(QLatin1StringView("encrypt-to-others box"));
0794 #endif
0795             group->setAlignment(Qt::AlignLeft);
0796             auto encGrid = new QGridLayout{group};
0797 
0798             for (auto it = std::begin(preferredKeys); it != std::end(preferredKeys); ++it) {
0799                 const auto &address = it.key();
0800                 const auto &keys = it.value();
0801                 if (address != mSender) {
0802                     addEncryptionAddr(address, preferredKeysProtocol, keys, alternativeKeysProtocol, alternativeKeys.value(address), encGrid);
0803                 }
0804             }
0805 
0806             encGrid->setColumnStretch(1, -1);
0807             mScrollLayout->addWidget(group);
0808         }
0809 
0810         mScrollLayout->addStretch(-1);
0811     }
0812 
0813     void updateOkButton()
0814     {
0815         static QString origOkText = mOkButton->text();
0816         const bool isGenerate = bool(findVisibleKeySelectionComboWithGenerateKey());
0817         const bool allVisibleEncryptionKeysAreIgnored = Kleo::all_of(mEncCombos, [](auto combo) {
0818             return !combo->isVisible() || combo->currentData(Qt::UserRole).toInt() == IgnoreKey;
0819         });
0820         const bool allVisibleEncryptionKeysAreUsable = Kleo::all_of(mEncCombos, [](auto combo) {
0821             return !combo->isVisible() || combo->currentKey().isNull() || Kleo::canBeUsedForEncryption(combo->currentKey());
0822         });
0823 
0824         // If we don't encrypt, then the OK button is always enabled. Likewise,
0825         // if the "generate key" option is selected. Otherwise,
0826         // we only enable it if we encrypt to at least one recipient.
0827         mOkButton->setEnabled(isGenerate || !mEncrypt || (!allVisibleEncryptionKeysAreIgnored && allVisibleEncryptionKeysAreUsable));
0828 
0829         mOkButton->setText(isGenerate ? i18n("Generate") : origOkText);
0830 
0831         if (!DeVSCompliance::isActive()) {
0832             return;
0833         }
0834 
0835         // Handle compliance
0836         bool de_vs = DeVSCompliance::isCompliant();
0837 
0838         if (de_vs) {
0839             const GpgME::Protocol protocol = currentProtocol();
0840 
0841             for (const auto combo : std::as_const(mAllCombos)) {
0842                 if (!combo->isVisible()) {
0843                     continue;
0844                 }
0845                 const auto key = combo->currentKey();
0846                 if (key.isNull()) {
0847                     continue;
0848                 }
0849                 if (protocol != GpgME::UnknownProtocol && key.protocol() != protocol) {
0850                     continue;
0851                 }
0852                 if (!DeVSCompliance::keyIsCompliant(key)) {
0853                     de_vs = false;
0854                     break;
0855                 }
0856             }
0857         }
0858 
0859         DeVSCompliance::decorate(mOkButton, de_vs);
0860         mComplianceLbl->setText(DeVSCompliance::name(de_vs));
0861         mComplianceLbl->setVisible(true);
0862     }
0863 
0864     GpgME::Protocol mForcedProtocol;
0865     QList<KeySelectionCombo *> mSigningCombos;
0866     QList<KeySelectionCombo *> mEncCombos;
0867     QList<KeySelectionCombo *> mAllCombos;
0868     QScrollArea *mScrollArea;
0869     QVBoxLayout *mScrollLayout;
0870     QPushButton *mOkButton;
0871     QVBoxLayout *mMainLay;
0872     QButtonGroup *mFormatBtns;
0873     QString mSender;
0874     bool mSign;
0875     bool mEncrypt;
0876     bool mAllowMixed;
0877     NewKeyApprovalDialog *q;
0878     QList<QGpgME::Job *> mRunningJobs;
0879     GpgME::Error mLastError;
0880     QLabel *mComplianceLbl;
0881     KeyResolver::Solution mAcceptedResult;
0882     QString mGenerateTooltip;
0883 };
0884 
0885 NewKeyApprovalDialog::NewKeyApprovalDialog(bool encrypt,
0886                                            bool sign,
0887                                            const QString &sender,
0888                                            KeyResolver::Solution preferredSolution,
0889                                            KeyResolver::Solution alternativeSolution,
0890                                            bool allowMixed,
0891                                            GpgME::Protocol forcedProtocol,
0892                                            QWidget *parent,
0893                                            Qt::WindowFlags f)
0894     : QDialog(parent, f)
0895     , d{std::make_unique<Private>(this, encrypt, sign, forcedProtocol, preferredSolution.protocol, sender, allowMixed)}
0896 {
0897     if (sign) {
0898         d->setSigningKeys(std::move(preferredSolution.signingKeys), std::move(alternativeSolution.signingKeys));
0899     }
0900     if (encrypt) {
0901         d->setEncryptionKeys(allowMixed ? UnknownProtocol : preferredSolution.protocol,
0902                              std::move(preferredSolution.encryptionKeys),
0903                              allowMixed ? UnknownProtocol : alternativeSolution.protocol,
0904                              std::move(alternativeSolution.encryptionKeys));
0905     }
0906     d->updateWidgets();
0907     d->updateOkButton();
0908 
0909     const auto size = sizeHint();
0910     const auto desk = screen()->size();
0911     resize(QSize(desk.width() / 3, qMin(size.height(), desk.height() / 2)));
0912 }
0913 
0914 Kleo::NewKeyApprovalDialog::~NewKeyApprovalDialog() = default;
0915 
0916 KeyResolver::Solution NewKeyApprovalDialog::result()
0917 {
0918     return d->mAcceptedResult;
0919 }
0920 
0921 void NewKeyApprovalDialog::handleKeyGenResult()
0922 {
0923     if (d->mRunningJobs.empty()) {
0924         qCWarning(LIBKLEO_LOG) << __func__ << "No running job";
0925     }
0926     const auto job = d->mRunningJobs.front();
0927     const auto result = QGpgME::Job::context(job)->keyGenerationResult();
0928     const auto combo = d->findVisibleKeySelectionComboWithGenerateKey();
0929     d->handleKeyGenResult(result, job, combo);
0930 }
0931 
0932 #include "newkeyapprovaldialog.moc"
0933 
0934 #include "moc_newkeyapprovaldialog.cpp"