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"