File indexing completed on 2024-06-23 05:13:50

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     crypto/gui/resultitemwidget.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
0008     SPDX-FileContributor: Intevation GmbH
0009 
0010     SPDX-License-Identifier: GPL-2.0-or-later
0011 */
0012 
0013 #include <config-kleopatra.h>
0014 
0015 #include "resultitemwidget.h"
0016 
0017 #include "commands/command.h"
0018 #include "commands/importcertificatefromfilecommand.h"
0019 #include "commands/lookupcertificatescommand.h"
0020 #include "crypto/decryptverifytask.h"
0021 #include "view/htmllabel.h"
0022 #include "view/urllabel.h"
0023 
0024 #include <Libkleo/AuditLogEntry>
0025 #include <Libkleo/AuditLogViewer>
0026 #include <Libkleo/Classify>
0027 #include <Libkleo/SystemInfo>
0028 
0029 #include <gpgme++/decryptionresult.h>
0030 #include <gpgme++/key.h>
0031 
0032 #include "kleopatra_debug.h"
0033 #include <KColorScheme>
0034 #include <KGuiItem>
0035 #include <KLocalizedString>
0036 #include <KStandardGuiItem>
0037 #include <QHBoxLayout>
0038 #include <QLabel>
0039 #include <QPushButton>
0040 #include <QUrl>
0041 #include <QVBoxLayout>
0042 
0043 using namespace Kleo;
0044 using namespace Kleo::Crypto;
0045 using namespace Kleo::Crypto::Gui;
0046 
0047 namespace
0048 {
0049 // TODO move out of here
0050 static QColor colorForVisualCode(Task::Result::VisualCode code)
0051 {
0052     switch (code) {
0053     case Task::Result::AllGood:
0054         return KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color();
0055     case Task::Result::NeutralError:
0056     case Task::Result::Warning:
0057         return KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NormalBackground).color();
0058     case Task::Result::Danger:
0059         return KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color();
0060     case Task::Result::NeutralSuccess:
0061     default:
0062         return QColor(0x00, 0x80, 0xFF); // light blue
0063     }
0064 }
0065 static QColor txtColorForVisualCode(Task::Result::VisualCode code)
0066 {
0067     switch (code) {
0068     case Task::Result::AllGood:
0069     case Task::Result::NeutralError:
0070     case Task::Result::Warning:
0071         return KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::NormalText).color();
0072     case Task::Result::Danger:
0073         return KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::NegativeText).color();
0074     case Task::Result::NeutralSuccess:
0075     default:
0076         return QColor(0xFF, 0xFF, 0xFF); // white
0077     }
0078 }
0079 }
0080 
0081 class ResultItemWidget::Private
0082 {
0083     ResultItemWidget *const q;
0084 
0085 public:
0086     explicit Private(const std::shared_ptr<const Task::Result> &result, ResultItemWidget *qq)
0087         : q(qq)
0088         , m_result(result)
0089     {
0090         Q_ASSERT(m_result);
0091     }
0092 
0093     void slotLinkActivated(const QString &);
0094     void updateShowDetailsLabel();
0095 
0096     void addKeyImportButton(QBoxLayout *lay, bool search);
0097     void addIgnoreMDCButton(QBoxLayout *lay);
0098 
0099     void oneImportFinished();
0100 
0101     const std::shared_ptr<const Task::Result> m_result;
0102     UrlLabel *m_auditLogLabel = nullptr;
0103     QPushButton *m_closeButton = nullptr;
0104     QPushButton *m_showButton = nullptr;
0105     bool m_importCanceled = false;
0106 };
0107 
0108 void ResultItemWidget::Private::oneImportFinished()
0109 {
0110     if (m_importCanceled) {
0111         return;
0112     }
0113     if (m_result->parentTask()) {
0114         m_result->parentTask()->start();
0115     }
0116     q->setVisible(false);
0117 }
0118 
0119 void ResultItemWidget::Private::addIgnoreMDCButton(QBoxLayout *lay)
0120 {
0121     if (!m_result || !lay) {
0122         return;
0123     }
0124 
0125     const auto dvResult = dynamic_cast<const DecryptVerifyResult *>(m_result.get());
0126     if (!dvResult) {
0127         return;
0128     }
0129     const auto decResult = dvResult->decryptionResult();
0130 
0131     if (decResult.isNull() || !decResult.error() || !decResult.isLegacyCipherNoMDC()) {
0132         return;
0133     }
0134 
0135     auto btn = new QPushButton(i18n("Force decryption"));
0136     btn->setFixedSize(btn->sizeHint());
0137 
0138     connect(btn, &QPushButton::clicked, q, [this]() {
0139         if (m_result->parentTask()) {
0140             const auto dvTask = dynamic_cast<DecryptVerifyTask *>(m_result->parentTask().data());
0141             dvTask->setIgnoreMDCError(true);
0142             dvTask->start();
0143             q->setVisible(false);
0144         } else {
0145             qCWarning(KLEOPATRA_LOG) << "Failed to get parent task";
0146         }
0147     });
0148     lay->addWidget(btn);
0149 }
0150 
0151 void ResultItemWidget::Private::addKeyImportButton(QBoxLayout *lay, bool search)
0152 {
0153     if (!m_result || !lay) {
0154         return;
0155     }
0156 
0157     const auto dvResult = dynamic_cast<const DecryptVerifyResult *>(m_result.get());
0158     if (!dvResult) {
0159         return;
0160     }
0161     const auto verifyResult = dvResult->verificationResult();
0162 
0163     if (verifyResult.isNull()) {
0164         return;
0165     }
0166 
0167     for (const auto &sig : verifyResult.signatures()) {
0168         if (!(sig.summary() & GpgME::Signature::KeyMissing)) {
0169             continue;
0170         }
0171 
0172         auto btn = new QPushButton;
0173         QString suffix;
0174         const auto keyid = QLatin1StringView(sig.fingerprint());
0175         if (verifyResult.numSignatures() > 1) {
0176             suffix = QLatin1Char(' ') + keyid;
0177         }
0178         btn = new QPushButton(search ? i18nc("1 is optional keyid. No space is intended as it can be empty.", "Search%1", suffix)
0179                                      : i18nc("1 is optional keyid. No space is intended as it can be empty.", "Import%1", suffix));
0180 
0181         if (search) {
0182             btn->setIcon(QIcon::fromTheme(QStringLiteral("edit-find")));
0183             connect(btn, &QPushButton::clicked, q, [this, btn, keyid]() {
0184                 btn->setEnabled(false);
0185                 m_importCanceled = false;
0186                 auto cmd = new Kleo::Commands::LookupCertificatesCommand(keyid, nullptr);
0187                 connect(cmd, &Kleo::Commands::LookupCertificatesCommand::canceled, q, [this]() {
0188                     m_importCanceled = true;
0189                 });
0190                 connect(cmd, &Kleo::Commands::LookupCertificatesCommand::finished, q, [this, btn]() {
0191                     btn->setEnabled(true);
0192                     oneImportFinished();
0193                 });
0194                 cmd->setParentWidget(q);
0195                 cmd->start();
0196             });
0197         } else {
0198             btn->setIcon(QIcon::fromTheme(QStringLiteral("view-certificate-import")));
0199             connect(btn, &QPushButton::clicked, q, [this, btn]() {
0200                 btn->setEnabled(false);
0201                 m_importCanceled = false;
0202                 auto cmd = new Kleo::ImportCertificateFromFileCommand();
0203                 connect(cmd, &Kleo::ImportCertificateFromFileCommand::canceled, q, [this]() {
0204                     m_importCanceled = true;
0205                 });
0206                 connect(cmd, &Kleo::ImportCertificateFromFileCommand::finished, q, [this, btn]() {
0207                     btn->setEnabled(true);
0208                     oneImportFinished();
0209                 });
0210                 cmd->setParentWidget(q);
0211                 cmd->start();
0212             });
0213         }
0214         btn->setFixedSize(btn->sizeHint());
0215         lay->addWidget(btn);
0216     }
0217 }
0218 
0219 static QUrl auditlog_url_template()
0220 {
0221     QUrl url(QStringLiteral("kleoresultitem://showauditlog"));
0222     return url;
0223 }
0224 
0225 void ResultItemWidget::Private::updateShowDetailsLabel()
0226 {
0227     const auto auditLogUrl = m_result->auditLog().asUrl(auditlog_url_template());
0228     const auto auditLogLinkText = m_result->hasError() ? i18n("Diagnostics") //
0229                                                        : i18nc("The Audit Log is a detailed error log from the gnupg backend", "Show Audit Log");
0230     m_auditLogLabel->setUrl(auditLogUrl, auditLogLinkText);
0231     m_auditLogLabel->setVisible(!auditLogUrl.isEmpty());
0232 }
0233 
0234 ResultItemWidget::ResultItemWidget(const std::shared_ptr<const Task::Result> &result, QWidget *parent, Qt::WindowFlags flags)
0235     : QWidget(parent, flags)
0236     , d(new Private(result, this))
0237 {
0238     const QColor color = colorForVisualCode(d->m_result->code());
0239     const QColor txtColor = txtColorForVisualCode(d->m_result->code());
0240     const QColor linkColor = txtColor;
0241     const QString styleSheet = SystemInfo::isHighContrastModeActive()
0242         ? QStringLiteral(
0243             "QFrame,QLabel { margin: 0px; }"
0244             "QFrame#resultFrame{ border-style: solid; border-radius: 3px; border-width: 1px }"
0245             "QLabel { padding: 5px; border-radius: 3px }")
0246         : QStringLiteral(
0247               "QFrame,QLabel { background-color: %1; margin: 0px; }"
0248               "QFrame#resultFrame{ border-color: %2; border-style: solid; border-radius: 3px; border-width: 1px }"
0249               "QLabel { color: %3; padding: 5px; border-radius: 3px }")
0250               .arg(color.name())
0251               .arg(color.darker(150).name())
0252               .arg(txtColor.name());
0253     auto topLayout = new QVBoxLayout(this);
0254     auto frame = new QFrame;
0255     frame->setObjectName(QLatin1StringView("resultFrame"));
0256     frame->setStyleSheet(styleSheet);
0257     topLayout->addWidget(frame);
0258     auto layout = new QHBoxLayout(frame);
0259     auto vlay = new QVBoxLayout();
0260     auto overview = new HtmlLabel;
0261     overview->setWordWrap(true);
0262     overview->setHtml(d->m_result->overview());
0263     overview->setStyleSheet(styleSheet);
0264     overview->setLinkColor(linkColor);
0265     setFocusPolicy(overview->focusPolicy());
0266     setFocusProxy(overview);
0267     connect(overview, &QLabel::linkActivated, this, [this](const auto &link) {
0268         d->slotLinkActivated(link);
0269     });
0270 
0271     vlay->addWidget(overview);
0272     layout->addLayout(vlay);
0273 
0274     auto actionLayout = new QVBoxLayout;
0275     layout->addLayout(actionLayout);
0276 
0277     d->addKeyImportButton(actionLayout, false);
0278     // TODO: Only show if auto-key-retrieve is not set.
0279     d->addKeyImportButton(actionLayout, true);
0280 
0281     d->addIgnoreMDCButton(actionLayout);
0282 
0283     d->m_auditLogLabel = new UrlLabel;
0284     connect(d->m_auditLogLabel, &QLabel::linkActivated, this, [this](const auto &link) {
0285         d->slotLinkActivated(link);
0286     });
0287     actionLayout->addWidget(d->m_auditLogLabel);
0288     d->m_auditLogLabel->setStyleSheet(styleSheet);
0289     d->m_auditLogLabel->setLinkColor(linkColor);
0290 
0291     auto detailsLabel = new HtmlLabel;
0292     detailsLabel->setWordWrap(true);
0293     detailsLabel->setHtml(d->m_result->details());
0294     detailsLabel->setStyleSheet(styleSheet);
0295     detailsLabel->setLinkColor(linkColor);
0296     connect(detailsLabel, &QLabel::linkActivated, this, [this](const auto &link) {
0297         d->slotLinkActivated(link);
0298     });
0299     vlay->addWidget(detailsLabel);
0300 
0301     d->m_showButton = new QPushButton;
0302     d->m_showButton->setVisible(false);
0303     connect(d->m_showButton, &QAbstractButton::clicked, this, &ResultItemWidget::showButtonClicked);
0304     actionLayout->addWidget(d->m_showButton);
0305 
0306     d->m_closeButton = new QPushButton;
0307     KGuiItem::assign(d->m_closeButton, KStandardGuiItem::close());
0308     d->m_closeButton->setFixedSize(d->m_closeButton->sizeHint());
0309     connect(d->m_closeButton, &QAbstractButton::clicked, this, &ResultItemWidget::closeButtonClicked);
0310     actionLayout->addWidget(d->m_closeButton);
0311     d->m_closeButton->setVisible(false);
0312 
0313     layout->setStretch(0, 1);
0314     actionLayout->addStretch(-1);
0315     vlay->addStretch(-1);
0316 
0317     d->updateShowDetailsLabel();
0318     setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Maximum);
0319 }
0320 
0321 ResultItemWidget::~ResultItemWidget()
0322 {
0323 }
0324 
0325 void ResultItemWidget::showCloseButton(bool show)
0326 {
0327     d->m_closeButton->setVisible(show);
0328 }
0329 
0330 void ResultItemWidget::setShowButton(const QString &text, bool show)
0331 {
0332     d->m_showButton->setText(text);
0333     d->m_showButton->setVisible(show);
0334 }
0335 
0336 bool ResultItemWidget::hasErrorResult() const
0337 {
0338     return d->m_result->hasError();
0339 }
0340 
0341 void ResultItemWidget::Private::slotLinkActivated(const QString &link)
0342 {
0343     Q_ASSERT(m_result);
0344     qCDebug(KLEOPATRA_LOG) << "Link activated: " << link;
0345     if (link.startsWith(QLatin1StringView("key:"))) {
0346         auto split = link.split(QLatin1Char(':'));
0347         auto fpr = split.value(1);
0348         if (split.size() == 2 && isFingerprint(fpr)) {
0349             /* There might be a security consideration here if somehow
0350              * a short keyid is used in a link and it collides with another.
0351              * So we additionally check that it really is a fingerprint. */
0352             auto cmd = Command::commandForQuery(fpr);
0353             cmd->setParentWId(q->effectiveWinId());
0354             cmd->start();
0355         } else {
0356             qCWarning(KLEOPATRA_LOG) << "key link invalid " << link;
0357         }
0358         return;
0359     }
0360 
0361     const QUrl url(link);
0362 
0363     if (url.host() == QLatin1StringView("showauditlog")) {
0364         q->showAuditLog();
0365         return;
0366     }
0367     qCWarning(KLEOPATRA_LOG) << "Unexpected link scheme: " << link;
0368 }
0369 
0370 void ResultItemWidget::showAuditLog()
0371 {
0372     AuditLogViewer::showAuditLog(parentWidget(), d->m_result->auditLog());
0373 }
0374 
0375 #include "moc_resultitemwidget.cpp"