newkeyapprovaldialog.cpp 27.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
/*  -*- c++ -*-
    newkeyapprovaldialog.cpp

    This file is part of libkleopatra, the KDE keymanagement library
    Copyright (c) 2018 Intevation GmbH

    Libkleopatra is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.

    Libkleopatra is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

    In addition, as a special exception, the copyright holders give
    permission to link the code of this program with any edition of
    the Qt library by Trolltech AS, Norway (or with modified versions
    of Qt that use the same license as Qt), and distribute linked
    combinations including the two.  You must obey the GNU General
    Public License in all respects for all of the code used other than
    Qt.  If you modify this file, you may extend this exception to
    your version of the file, but you are not obligated to do so.  If
    you do not wish to do so, delete this exception statement from
    your version.
*/

#include "newkeyapprovaldialog.h"
#include "kleo/defaultkeyfilter.h"
#include "keyselectioncombo.h"
#include "progressdialog.h"
#include "utils/formatting.h"


#include "libkleo_debug.h"

#include <QApplication>
#include <QButtonGroup>
#include <QDesktopWidget>
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QLabel>
#include <QMap>
#include <QPushButton>
#include <QRadioButton>
#include <QScrollArea>
52
#include <QToolTip>
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
#include <QVBoxLayout>

#include <QGpgME/DefaultKeyGenerationJob>
#include <QGpgME/CryptoConfig>
#include <QGpgME/Protocol>
#include <gpgme++/keygenerationresult.h>
#include <gpgme++/key.h>

#include <KLocalizedString>
#include <KMessageBox>

using namespace Kleo;

namespace {
class OpenPGPFilter: public DefaultKeyFilter
{
public:
    OpenPGPFilter() : DefaultKeyFilter()
    {
        setIsOpenPGP(DefaultKeyFilter::Set);
73
        setCanEncrypt(DefaultKeyFilter::Set);
74 75 76 77 78 79 80 81 82
    }
};
static std::shared_ptr<KeyFilter> s_pgpFilter = std::shared_ptr<KeyFilter> (new OpenPGPFilter);
class OpenPGPSignFilter: public DefaultKeyFilter
{
public:
    OpenPGPSignFilter() : DefaultKeyFilter()
    {
        /* Also list unusable keys to make it transparent why they are unusable */
83 84 85 86
        setDisabled(DefaultKeyFilter::NotSet);
        setRevoked(DefaultKeyFilter::NotSet);
        setExpired(DefaultKeyFilter::NotSet);
        setCanSign(DefaultKeyFilter::Set);
87 88 89 90 91 92 93 94 95 96 97
        setHasSecret(DefaultKeyFilter::Set);
        setIsOpenPGP(DefaultKeyFilter::Set);
    }
};
static std::shared_ptr<KeyFilter> s_pgpSignFilter = std::shared_ptr<KeyFilter> (new OpenPGPSignFilter);
class SMIMEFilter: public DefaultKeyFilter
{
public:
    SMIMEFilter(): DefaultKeyFilter()
    {
        setIsOpenPGP(DefaultKeyFilter::NotSet);
98
        setCanEncrypt(DefaultKeyFilter::Set);
99 100 101 102 103 104 105 106
    }
};
static std::shared_ptr<KeyFilter> s_smimeFilter = std::shared_ptr<KeyFilter> (new SMIMEFilter);
class SMIMESignFilter: public DefaultKeyFilter
{
public:
    SMIMESignFilter(): DefaultKeyFilter()
    {
107 108 109 110
        setDisabled(DefaultKeyFilter::NotSet);
        setRevoked(DefaultKeyFilter::NotSet);
        setExpired(DefaultKeyFilter::NotSet);
        setCanSign(DefaultKeyFilter::Set);
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
        setIsOpenPGP(DefaultKeyFilter::NotSet);
        setHasSecret(DefaultKeyFilter::Set);
    }
};
static std::shared_ptr<KeyFilter> s_smimeSignFilter = std::shared_ptr<KeyFilter> (new SMIMESignFilter);
static std::shared_ptr<KeyFilter> s_defaultFilter= std::shared_ptr<KeyFilter> (new DefaultKeyFilter);
class SignFilter: public DefaultKeyFilter
{
public:
    SignFilter(): DefaultKeyFilter()
    {
        setHasSecret(DefaultKeyFilter::Set);
    }
};
static std::shared_ptr<KeyFilter> s_signFilter = std::shared_ptr<KeyFilter> (new SignFilter);

/* Some decoration and a button to remove the filter for a keyselectioncombo */
class ComboWidget: public QWidget
{
    Q_OBJECT
public:
    explicit ComboWidget(KeySelectionCombo *combo):
        mCombo(combo),
134 135
        mFilterBtn(new QPushButton),
        mFromOverride(GpgME::UnknownProtocol)
136 137
    {
        auto hLay = new QHBoxLayout(this);
138
        auto infoBtn = new QPushButton;
Laurent Montel's avatar
Laurent Montel committed
139
        infoBtn->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual")));
140 141 142
        infoBtn->setIconSize(QSize(22,22));
        infoBtn->setFlat(true);
        hLay->addWidget(infoBtn);
143 144 145
        hLay->addWidget(combo, 1);
        hLay->addWidget(mFilterBtn, 0);

146 147 148 149 150
        connect(infoBtn, &QPushButton::clicked, this, [this, infoBtn] () {
            QToolTip::showText(infoBtn->mapToGlobal(QPoint()) + QPoint(infoBtn->width(), 0),
                    mCombo->currentData(Qt::ToolTipRole).toString(), infoBtn, QRect(), 30000);
        });

151
        // Assume that combos start out with a filter
Laurent Montel's avatar
Laurent Montel committed
152
        mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters")));
153 154 155 156 157 158 159 160 161 162 163 164
        mFilterBtn->setToolTip(i18n("Remove Filter"));

        // FIXME: This is ugly to enforce but otherwise the
        // icon is broken.
        combo->setMinimumHeight(22);
        mFilterBtn->setMinimumHeight(23);

        connect(mFilterBtn, &QPushButton::clicked, this, [this] () {
            const QString curFilter = mCombo->idFilter();
            if (curFilter.isEmpty()) {
                mCombo->setIdFilter(mLastIdFilter);
                mLastIdFilter = QString();
Laurent Montel's avatar
Laurent Montel committed
165
                mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters")));
166 167 168
                mFilterBtn->setToolTip(i18n("Remove Filter"));
            } else {
                mLastIdFilter = curFilter;
Laurent Montel's avatar
Laurent Montel committed
169
                mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-add-filters")));
170 171 172 173 174 175 176 177 178 179 180
                mFilterBtn->setToolTip(i18n("Add Filter"));
                mCombo->setIdFilter(QString());
            }
        });
    }

    KeySelectionCombo *combo()
    {
        return mCombo;
    }

181 182 183 184 185 186 187 188 189 190
    GpgME::Protocol fromOverride() const
    {
        return mFromOverride;
    }

    void setFromOverride(GpgME::Protocol proto)
    {
        mFromOverride = proto;
    }

191 192 193 194
private:
    KeySelectionCombo *mCombo;
    QPushButton *mFilterBtn;
    QString mLastIdFilter;
195
    GpgME::Protocol mFromOverride;
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
};

static enum GpgME::UserID::Validity keyValidity(const GpgME::Key &key)
{
    enum GpgME::UserID::Validity validity = GpgME::UserID::Validity::Unknown;

    for (const auto &uid: key.userIDs()) {
        if (validity == GpgME::UserID::Validity::Unknown
            || validity > uid.validity()) {
            validity = uid.validity();
        }
    }

    return validity;
}
211 212 213 214 215 216 217 218 219 220 221

static bool key_has_addr(const GpgME::Key &key, const QString &addr)
{
    for (const auto &uid: key.userIDs()) {
        if (QString::fromStdString(uid.addrSpec()).toLower() == addr.toLower()) {
            return true;
        }
    }
    return false;
}

222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
} // namespace

class NewKeyApprovalDialog::Private
{
private:
    enum Action {
        Unset,
        GenerateKey,
        IgnoreKey,
    };
public:
    Private(NewKeyApprovalDialog *pub,
            GpgME::Protocol forcedProtocol,
            GpgME::Protocol presetProtocol,
            const QString &sender, bool allowMixed):
        mProto(forcedProtocol),
        mSender(sender),
        mAllowMixed(allowMixed),
        q(pub)
    {
242 243 244 245 246 247 248
        // We do the translation here to avoid having the same string multiple times.
        mGenerateTooltip = i18nc("@info:tooltip for a 'Generate new key pair' action "
                                 "in a combobox when a user does not yet have an OpenPGP or S/MIME key.",
                                 "Generate a new key using your E-Mail address.<br/><br/>"
                                 "The key is necessary to decrypt and sign E-Mails. "
                                 "You will be asked for a passphrase to protect this key and the protected key "
                                 "will be stored in your home directory.");
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
        mMainLay = new QVBoxLayout;

        QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
        mOkButton = btnBox->button(QDialogButtonBox::Ok);
        QObject::connect (btnBox, &QDialogButtonBox::accepted, q, [this] () {
                accepted();
            });
        QObject::connect (btnBox, &QDialogButtonBox::rejected, q, &QDialog::reject);

        mScrollArea = new QScrollArea;
        mScrollArea->setWidget(new QWidget);
        mScrollLayout = new QVBoxLayout;
        mScrollArea->widget()->setLayout(mScrollLayout);
        mScrollArea->setWidgetResizable(true);
        mScrollArea->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow);
        mScrollArea->setFrameStyle(QFrame::NoFrame);
Laurent Montel's avatar
Laurent Montel committed
265
        mScrollLayout->setContentsMargins(0, 0, 0, 0);
266

267
        q->setWindowTitle(i18nc("@title:window", "Security approval"));
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302

        auto fmtLayout = new QHBoxLayout;
        mFormatBtns = new QButtonGroup;
        auto pgpBtn = new QRadioButton(i18n("OpenPGP"));
        auto smimeBtn = new QRadioButton(i18n("S/MIME"));
        mFormatBtns->addButton(pgpBtn, 1);
        mFormatBtns->addButton(smimeBtn, 2);
        mFormatBtns->setExclusive(true);

        fmtLayout->addStretch(-1);
        fmtLayout->addWidget(pgpBtn);
        fmtLayout->addWidget(smimeBtn);
        mMainLay->addLayout(fmtLayout);

        // Handle force / preset
        if (forcedProtocol == GpgME::OpenPGP) {
            pgpBtn->setChecked(true);
            pgpBtn->setVisible(false);
            smimeBtn->setVisible(false);
        } else if (forcedProtocol == GpgME::CMS) {
            smimeBtn->setChecked(true);
            pgpBtn->setVisible(false);
            smimeBtn->setVisible(false);
        } else if (presetProtocol == GpgME::CMS) {
            smimeBtn->setChecked(true);
        } else if (!mAllowMixed) {
            pgpBtn->setChecked(true);
        } else if (mAllowMixed) {
            smimeBtn->setVisible(false);
            pgpBtn->setVisible(false);
        }

        updateFilter();


Laurent Montel's avatar
Laurent Montel committed
303 304
        QObject::connect (mFormatBtns, static_cast<void (QButtonGroup::*)(QAbstractButton *, bool)> (&QButtonGroup::buttonToggled),
                q, [this](QAbstractButton *, bool) {
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
            updateFilter();
        });

        mMainLay->addWidget(mScrollArea);

        mComplianceLbl = new QLabel;
        mComplianceLbl->setVisible(false);

        auto btnLayout = new QHBoxLayout;
        btnLayout->addWidget(mComplianceLbl);
        btnLayout->addWidget(btnBox);
        mMainLay->addLayout(btnLayout);

        q->setLayout(mMainLay);

    }

    void generateKey(KeySelectionCombo *combo)
    {
        const auto &addr = combo->property("address").toString();
        auto job = new QGpgME::DefaultKeyGenerationJob(q);
326
        auto progress = new Kleo::ProgressDialog(job, i18n("Generating key for '%1'...", addr) + QStringLiteral("\n\n") +
Yuri Chornoivan's avatar
Yuri Chornoivan committed
327
                                                 i18n("This can take several minutes."), q);
328
        progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowContextHelpButtonHint);
329
        progress->setWindowTitle(i18nc("@title:window", "Key generation"));
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
        progress->setModal(true);
        progress->setAutoClose(true);
        progress->setMinimumDuration(0);
        progress->setValue(0);

        mRunningJobs << job;
        connect (job, &QGpgME::DefaultKeyGenerationJob::result, q,
            [this, job, combo] (const GpgME::KeyGenerationResult &result) {
                handleKeyGenResult(result, job, combo);
            });
        job->start(addr, QString());
        return;
    }

    void handleKeyGenResult(const GpgME::KeyGenerationResult &result, QGpgME::Job *job, KeySelectionCombo *combo)
    {
        mLastError = result.error();
        if (!mLastError || mLastError.isCanceled()) {
348
            combo->setDefaultKey(QString::fromLatin1(result.fingerprint()), GpgME::OpenPGP);
349 350 351 352 353 354 355 356 357 358 359 360
            connect (combo, &KeySelectionCombo::keyListingFinished, q, [this, job] () {
                    mRunningJobs.removeAll(job);
                });
            combo->refreshKeys();
        } else {
            mRunningJobs.removeAll(job);
        }
    }

    void checkAccepted()
    {
        if (mLastError || mLastError.isCanceled()) {
Laurent Montel's avatar
Laurent Montel committed
361
            KMessageBox::error(q, QString::fromLocal8Bit(mLastError.asString()), i18n("Operation Failed"));
362 363 364
            mRunningJobs.clear();
            return;
        }
365 366 367

        if (!mRunningJobs.empty()) {
            return;
368
        }
369 370 371 372 373 374 375
        /* Save the keys */
        bool isPGP = mFormatBtns->checkedId() == 1;
        bool isSMIME = mFormatBtns->checkedId() == 2;

        mAcceptedEnc.clear();
        mAcceptedSig.clear();

Laurent Montel's avatar
Laurent Montel committed
376
        for (const auto combo: qAsConst(mEncCombos)) {
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
            const auto &addr = combo->property("address").toString();
            const auto &key = combo->currentKey();
            if (!combo->isVisible()) {
                continue;
            }
            if (isSMIME && key.protocol() != GpgME::CMS) {
                continue;
            }
            if (isPGP && key.protocol() != GpgME::OpenPGP) {
                continue;
            }
            if (mAcceptedEnc.contains(addr)) {
                mAcceptedEnc[addr].push_back(key);
            } else {
                std::vector<GpgME::Key> vec;
                vec.push_back(key);
                mAcceptedEnc.insert(addr, vec);
            }
        }
Laurent Montel's avatar
Laurent Montel committed
396
        for (const auto combo: qAsConst(mSigningCombos)) {
397 398 399 400 401 402 403 404 405 406 407 408 409 410
            const auto key = combo->currentKey();
            if (!combo->isVisible()) {
                continue;
            }
            if (isSMIME && key.protocol() != GpgME::CMS) {
                continue;
            }
            if (isPGP && key.protocol() != GpgME::OpenPGP) {
                continue;
            }
            mAcceptedSig.push_back(combo->currentKey());
        }

        q->accept();
411 412 413 414 415 416 417
    }

    void accepted()
    {
        // We can assume everything was validly resolved, otherwise
        // the OK button would have been disabled.
        // Handle custom items now.
Laurent Montel's avatar
Laurent Montel committed
418
        for (auto combo: qAsConst(mAllCombos)) {
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
            auto act = combo->currentData(Qt::UserRole).toInt();
            if (act == GenerateKey) {
                generateKey(combo);
                // Only generate once
                return;
            }
        }
        checkAccepted();
    }

    void updateFilter()
    {
        bool isPGP = mFormatBtns->checkedId() == 1;
        bool isSMIME = mFormatBtns->checkedId() == 2;

        if (isSMIME) {
            mCurEncFilter = s_smimeFilter;
            mCurSigFilter = s_smimeSignFilter;
        } else if (isPGP) {
            mCurEncFilter = s_pgpFilter;
            mCurSigFilter = s_pgpSignFilter;
        } else {
            mCurEncFilter = s_defaultFilter;
            mCurSigFilter = s_signFilter;
        }
Laurent Montel's avatar
Laurent Montel committed
444
        for (auto combo: qAsConst(mSigningCombos)) {
445
            combo->setKeyFilter(mCurSigFilter);
446 447 448 449 450 451 452 453
            auto widget = qobject_cast <ComboWidget *>(combo->parentWidget());
            if (!widget) {
                qCDebug(LIBKLEO_LOG) << "Failed to find signature combo widget";
                continue;
            }
            widget->setVisible(widget->fromOverride() == GpgME::UnknownProtocol ||
                               ((isSMIME && widget->fromOverride() == GpgME::CMS) ||
                                (isPGP && widget->fromOverride() == GpgME::OpenPGP)));
454
        }
Laurent Montel's avatar
Laurent Montel committed
455
        for (auto combo: qAsConst(mEncCombos)) {
456
            combo->setKeyFilter(mCurEncFilter);
457 458 459 460 461 462 463 464 465

            auto widget = qobject_cast <ComboWidget *>(combo->parentWidget());
            if (!widget) {
                qCDebug(LIBKLEO_LOG) << "Failed to find combo widget";
                continue;
            }
            widget->setVisible(widget->fromOverride() == GpgME::UnknownProtocol ||
                               ((isSMIME && widget->fromOverride() == GpgME::CMS) ||
                                (isPGP && widget->fromOverride() == GpgME::OpenPGP)));
466 467 468 469 470 471 472 473
        }
    }

    ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key)
    {
        auto combo = new KeySelectionCombo();
        combo->setKeyFilter(mCurSigFilter);
        if (!key.isNull()) {
474
            combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), key.protocol());
475 476 477
        }
        if (key.isNull() && mProto != GpgME::CMS) {
            combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")),
478 479
                                    i18n("Generate a new key pair"), GenerateKey,
                                    mGenerateTooltip);
480 481
        }
        combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("emblem-unavailable")),
482 483 484
                i18n("Don't confirm identity and integrity"), IgnoreKey,
                i18nc("@info:tooltip for not selecting a key for signing.",
                      "The E-Mail will not be cryptographically signed."));
485 486 487 488 489 490 491 492

        mSigningCombos << combo;
        mAllCombos << combo;
        combo->setProperty("address", addr);

        connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this] () {
            updateOkButton();
        });
493 494 495
        connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged), q, [this] () {
            updateOkButton();
        });
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516

        return new ComboWidget(combo);
    }

    void addSigningKeys(const QMap<QString, std::vector<GpgME::Key> > &resolved,
                        const QStringList &unresolved)
    {
        if (resolved.empty() && unresolved.empty()) {
            return;
        }
        for (const QString &addr: resolved.keys()) {
            auto group = new QGroupBox(i18nc("Caption for signing key selection",
                                             "Confirm identity '%1' as:", addr));
            group->setAlignment(Qt::AlignLeft);
            mScrollLayout->addWidget(group);
            auto sigLayout = new QVBoxLayout;

            group->setLayout(sigLayout);
            for (const auto &key: resolved[addr])
            {
                auto comboWidget = createSigningCombo(addr, key);
517
                if (key_has_addr (key, addr)) {
518 519
                    comboWidget->combo()->setIdFilter(addr);
                }
520 521 522
                if (resolved[addr].size() > 1) {
                    comboWidget->setFromOverride(key.protocol());
                }
523 524 525
                sigLayout->addWidget(comboWidget);
            }
        }
Laurent Montel's avatar
Laurent Montel committed
526
        for (const QString &addr: qAsConst(unresolved)) {
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
            auto group = new QGroupBox(i18nc("Caption for signing key selection, no key found",
                                             "No key found for the address '%1':", addr));
            group->setAlignment(Qt::AlignLeft);
            mScrollLayout->addWidget(group);
            auto sigLayout = new QHBoxLayout;

            group->setLayout(sigLayout);

            auto comboWidget = createSigningCombo(addr, GpgME::Key());
            comboWidget->combo()->setIdFilter(addr);
            sigLayout->addWidget(comboWidget);
        }
    }

    void addEncryptionAddr(const QString &addr, const std::vector<GpgME::Key> &keys,
                           QGridLayout *encGrid)
    {
        encGrid->addWidget(new QLabel(addr), encGrid->rowCount(), 0);
        for (const auto &key: keys)
        {
            auto combo = new KeySelectionCombo(false);
            combo->setKeyFilter(mCurEncFilter);
            if (!key.isNull()) {
550 551
                combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()),
                                     key.protocol());
552 553 554 555
            }

            if (mSender == addr && key.isNull()) {
                combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")),
556 557
                                        i18n("Generate a new key pair"), GenerateKey,
                                        mGenerateTooltip);
558 559 560
            }

            combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("emblem-unavailable")),
561
                    i18n("No key. Recipient will be unable to decrypt."), IgnoreKey,
562 563 564 565
                    i18nc("@info:tooltip for No Key selected for a specific recipient.",
                          "Do not select a key for this recipient.<br/><br/>"
                          "The recipient will receive the encrypted E-Mail, but it can only "
                          "be decrypted with the other keys selected in this dialog."));
566

567
            if (key.isNull() || key_has_addr (key, addr)) {
568 569 570 571
                combo->setIdFilter(addr);
            }

            connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this] () {
572 573 574 575
                updateOkButton();
            });
            connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged), q, [this] () {
                updateOkButton();
576 577 578 579 580 581
            });

            mEncCombos << combo;
            mAllCombos << combo;
            combo->setProperty("address", addr);
            auto comboWidget = new ComboWidget(combo);
582 583 584
            if (keys.size() > 1) {
                comboWidget->setFromOverride(key.protocol());
            }
585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
            encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
        }
    }

    void addEncryptionKeys(const QMap<QString, std::vector<GpgME::Key> > &resolved,
                           const QStringList &unresolved)
    {
        if (resolved.empty() && unresolved.empty()) {
            return;
        }
        auto group = new QGroupBox(i18n("Encrypt to:"));
        group->setAlignment(Qt::AlignLeft);
        auto encGrid = new QGridLayout;
        group->setLayout(encGrid);
        mScrollLayout->addWidget(group);

        for (const QString &addr: resolved.keys()) {
            addEncryptionAddr(addr, resolved[addr], encGrid);
        }
        std::vector<GpgME::Key> dummy;
        dummy.push_back(GpgME::Key());
        for (const QString &addr: unresolved) {
            addEncryptionAddr(addr, dummy, encGrid);
        }

        encGrid->setColumnStretch(1, -1);
        mScrollLayout->addStretch(-1);
    }

    void updateOkButton()
    {
        static QString origOkText = mOkButton->text();
617
        bool isGenerate = false;
618
        bool isAllIgnored = true;
619 620 621 622 623
        // Check if generate is selected.
        for (auto combo: mAllCombos) {
            auto act = combo->currentData(Qt::UserRole).toInt();
            if (act == GenerateKey) {
                mOkButton->setText(i18n("Generate"));
624
                isGenerate = true;
625
            }
626 627 628
            if (act != IgnoreKey) {
                isAllIgnored = false;
            }
629
        }
630 631 632

        mOkButton->setEnabled(!isAllIgnored);

633 634 635
        if (!isGenerate) {
            mOkButton->setText(origOkText);
        }
636

637
        if (Formatting::complianceMode() != QLatin1String("de-vs")) {
638 639 640 641 642
            return;
        }

        // Handle compliance
        bool de_vs = true;
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658

        bool isPGP = mFormatBtns->checkedId() == 1;
        bool isSMIME = mFormatBtns->checkedId() == 2;

        for (const auto combo: qAsConst(mEncCombos)) {
            const auto &key = combo->currentKey();
            if (!combo->isVisible()) {
                continue;
            }
            if (isSMIME && key.protocol() != GpgME::CMS) {
                continue;
            }
            if (isPGP && key.protocol() != GpgME::OpenPGP) {
                continue;
            }
            qCDebug(LIBKLEO_LOG) << "Checking" << key.primaryFingerprint() << Formatting::isKeyDeVs(key);
659 660 661 662 663 664
            if (!Formatting::isKeyDeVs(key) || keyValidity(key) < GpgME::UserID::Validity::Full) {
                de_vs = false;
                break;
            }
        }
        if (de_vs) {
665 666 667 668 669 670 671 672 673 674
            for (const auto combo: qAsConst(mSigningCombos)) {
                const auto key = combo->currentKey();
                if (!combo->isVisible()) {
                    continue;
                }
                if (isSMIME && key.protocol() != GpgME::CMS) {
                    continue;
                }
                if (isPGP && key.protocol() != GpgME::OpenPGP) {
                    continue;
675
                }
676 677
                if (!Formatting::isKeyDeVs(key) || keyValidity(key) < GpgME::UserID::Validity::Full) {
                    de_vs = false;
678 679 680 681 682 683
                    break;
                }
            }
        }

        mOkButton->setIcon(QIcon::fromTheme(de_vs
Laurent Montel's avatar
Laurent Montel committed
684 685
                    ? QStringLiteral("security-high")
                    : QStringLiteral("security-medium")));
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
        mOkButton->setStyleSheet(QStringLiteral("background-color: ") + (de_vs
                    ? QStringLiteral("#D5FAE2")  // KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name()
                    : QStringLiteral("#FAE9EB"))); //KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name()));
        mComplianceLbl->setText(de_vs
                ? i18nc("VS-NfD-conforming is a German standard for restricted documents for which special restrictions about algorithms apply.  The string states that all cryptographic operations necessary for the communication are compliant with that.",
                    "VS-NfD-compliant communication possible.")
                : i18nc("VS-NfD-conforming is a German standard for restricted documents for which special restrictions about algorithms apply.  The string states that all cryptographic operations necessary for the communication are compliant with that.",
                    "VS-NfD-compliant communication not possible."));
        mComplianceLbl->setVisible(true);
    }

    void selectionChanged()
    {
        bool isPGP = false;
        bool isCMS = false;
        for (const auto combo: mEncCombos) {
            isPGP |= combo->currentKey().protocol() == GpgME::OpenPGP;
            isCMS |= combo->currentKey().protocol() == GpgME::CMS;
            if (isPGP && isCMS) {
                break;
            }
        }
    }

    ~Private() {}

    GpgME::Protocol mProto;
    QList<KeySelectionCombo *> mSigningCombos;
    QList<KeySelectionCombo *> mEncCombos;
    QList<KeySelectionCombo *> mAllCombos;
    QScrollArea *mScrollArea;
    QVBoxLayout *mScrollLayout;
    QPushButton *mOkButton;
    QVBoxLayout *mMainLay;
    QButtonGroup *mFormatBtns;
    std::shared_ptr<KeyFilter> mCurSigFilter;
    std::shared_ptr<KeyFilter> mCurEncFilter;
    QString mSender;
    bool mAllowMixed;
    NewKeyApprovalDialog *q;
    QList <QGpgME::Job *> mRunningJobs;
    GpgME::Error mLastError;
    QLabel *mComplianceLbl;
729 730
    QMap<QString, std::vector<GpgME::Key> > mAcceptedEnc;
    std::vector<GpgME::Key> mAcceptedSig;
731
    QString mGenerateTooltip;
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747
};

NewKeyApprovalDialog::NewKeyApprovalDialog(const QMap<QString, std::vector<GpgME::Key> > &resolvedSigningKeys,
                                           const QMap<QString, std::vector<GpgME::Key> > &resolvedRecp,
                                           const QStringList &unresolvedSigKeys,
                                           const QStringList &unresolvedRecp,
                                           const QString &sender,
                                           bool allowMixed,
                                           GpgME::Protocol forcedProtocol,
                                           GpgME::Protocol presetProtocol,
                                           QWidget *parent,
                                           Qt::WindowFlags f): QDialog(parent, f),
                                                             d(new Private(this, forcedProtocol, presetProtocol, sender, allowMixed))
{
    d->addSigningKeys(resolvedSigningKeys, unresolvedSigKeys);
    d->addEncryptionKeys(resolvedRecp, unresolvedRecp);
748
    d->updateFilter();
749 750 751 752 753 754 755 756 757
    d->updateOkButton();

    const auto size = sizeHint();
    const auto desk = QApplication::desktop()->screenGeometry(this);
    resize(QSize(desk.width() / 3, qMin(size.height(), desk.height() / 2)));
}

std::vector<GpgME::Key> NewKeyApprovalDialog::signingKeys()
{
758
    return d->mAcceptedSig;
759 760 761 762
}

QMap <QString, std::vector<GpgME::Key> > NewKeyApprovalDialog::encryptionKeys()
{
763
    return d->mAcceptedEnc;
764 765 766
}

#include "newkeyapprovaldialog.moc"