Commit 6846952d authored by Stefano Crocco's avatar Stefano Crocco
Browse files

Display more information about the certificate error

parent b3e85e47
......@@ -522,6 +522,10 @@ void KonqMainWindow::openUrl(KonqView *_view, const QUrl &_url,
QUrl url(_url);
QString mimeType(_mimeType);
KonqOpenURLRequest req(_req);
if (m_currentView && url.isLocalFile() && m_currentView->isWebEngineView()) {
//The value of the entry doesn't matter: what's important is that the key exists
req.args.metaData().insert("urlRequestedByApp", QString()) ;
}
if (mimeType.isEmpty()) {
mimeType = req.args.mimeType();
......@@ -641,7 +645,6 @@ void KonqMainWindow::openUrl(KonqView *_view, const QUrl &_url,
}
}
}
const bool hasMimeType = (!mimeType.isEmpty() && mimeType != QLatin1String("application/octet-stream"));
KService::Ptr offer;
bool associatedAppIsKonqueror = false;
......
......@@ -212,6 +212,9 @@ void KonqView::openUrl(const QUrl &url, const QString &locationBarURL,
aboutToOpenURL(url, args);
if (args.metaData().contains("urlRequestedByApp") && isWebEngineView()) {
m_pPart->setProperty("urlRequestedByApp", url);
}
m_pPart->openUrl(url);
updateHistoryEntry(true);
......
......@@ -34,9 +34,10 @@ set(kwebenginepartlib_LIB_SRCS
webengineurlrequestinterceptor.cpp
spellcheckermanager.cpp
webenginepartcontrols.cpp
webenginepartcertificateerrordlg.cpp
)
ki18n_wrap_ui(kwebenginepartlib_LIB_SRCS webenginecustomizecacheablefieldsdlg.ui ui/credentialsdetailswidget.ui)
ki18n_wrap_ui(kwebenginepartlib_LIB_SRCS webenginecustomizecacheablefieldsdlg.ui ui/credentialsdetailswidget.ui webenginepartcertificateerrordlg.ui)
qt5_add_resources(kwebenginepartlib_LIB_SRCS webenginepart.qrc)
......@@ -62,6 +63,7 @@ target_link_libraries(kwebenginepartlib
KF5::Parts
KF5::Wallet
KF5::Notifications
KF5::KIOWidgets
PRIVATE
KF5::Konq
KF5::I18n
......
......@@ -16,6 +16,7 @@
#include "webenginepartdownloadmanager.h"
#include "webenginewallet.h"
#include <webenginepart_debug.h>
#include "webenginepartcertificateerrordlg.h"
#include <QWebEngineCertificateError>
#include <QWebEngineSettings>
......@@ -43,9 +44,6 @@
#include <QDesktopWidget>
#include <QFileDialog>
#include <QDialogButtonBox>
#include <QDialog>
#include <QPushButton>
#include <QMessageBox>
#include <QFile>
#include <QAuthenticator>
......@@ -60,7 +58,6 @@
//#include <QWebSecurityOrigin>
#include "utils.h"
WebEnginePage::WebEnginePage(WebEnginePart *part, QWidget *parent)
: QWebEnginePage(parent),
m_kioErrorCode(0),
......@@ -213,8 +210,8 @@ static bool domainSchemeMatch(const QUrl& u1, const QUrl& u2)
bool WebEnginePage::acceptNavigationRequest(const QUrl& url, NavigationType type, bool isMainFrame)
{
if (m_urlLoadedByPart != url) {
m_urlLoadedByPart = QUrl();
if (m_urlRequestedByApp != url) {
m_urlRequestedByApp = QUrl();
//Don't open local files using WebEnginePart except if configured to do so by the user. For example
//for example, this ensures that the "Home" link in the introduction page is opened in Dolphin part
......@@ -351,8 +348,9 @@ static int errorCodeFromReply(QNetworkReply* reply)
}
#endif
bool WebEnginePage::certificateError(const QWebEngineCertificateError& ce)
bool WebEnginePage::certificateError(const QWebEngineCertificateError& _ce)
{
QWebEngineCertificateError ce(_ce);
if (!ce.isOverridable()) {
return false;
}
......@@ -363,24 +361,33 @@ bool WebEnginePage::certificateError(const QWebEngineCertificateError& ce)
if (exceptionsForUrl.contains(error)) {
return true;
} else {
QString translatedDesc = i18n(ce.errorDescription().toUtf8());
QString text = i18n("<p>The server failed the authenticity check (%1). The error is:</p><p><tt>%2</tt></p>Do you want to ignore this error?",
ce.url().host(), translatedDesc);
QDialog *msgBox = new QDialog(view());
QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Yes|QDialogButtonBox::YesToAll|QDialogButtonBox::No, msgBox);
btnBox->button(QDialogButtonBox::Yes)->setText(i18nc("Ignore the certificate error for this URL only for now", "Yes, &once"));
btnBox->button(QDialogButtonBox::YesToAll)->setText(i18nc("Ignore the certificate error for this URL now and in the future", "Yes, &forever"));
QDialogButtonBox::StandardButton ans = KMessageBox::createKMessageBox(msgBox, btnBox, QMessageBox::Warning, text, {}, QString(), nullptr,
KMessageBox::Notify|KMessageBox::AllowLink|KMessageBox::Dangerous);
if (ans == QDialogButtonBox::No) {
return false;
}
if (ans == QDialogButtonBox::YesToAll) {
exceptionsForUrl.append(error);
ce.defer();
WebEnginePartCertificateErrorDlg *dlg = new WebEnginePartCertificateErrorDlg(ce, view());
connect(dlg, &WebEnginePartCertificateErrorDlg::finished, this, [this, dlg](int _){handleCertificateError(dlg);});
dlg->open();
return true;
}
}
void WebEnginePage::handleCertificateError(WebEnginePartCertificateErrorDlg *dlg) {
if (!dlg) {
return;
}
QWebEngineCertificateError error = dlg->certificateError();
WebEnginePartCertificateErrorDlg::UserChoice choice = dlg->userChoice();
dlg->deleteLater();
if (choice == WebEnginePartCertificateErrorDlg::UserChoice::DontIgnoreError) {
error.rejectCertificate();
} else {
error.ignoreCertificateError();
if (choice == WebEnginePartCertificateErrorDlg::UserChoice::IgnoreErrorForever) {
KConfigGroup grp(KSharedConfig::openConfig(), "CertificateExceptions");
QString url = error.url().url();
QList<int> exceptionsForUrl = grp.readEntry(url, QList<int>{});
exceptionsForUrl.append(error.error());
grp.writeEntry(url, exceptionsForUrl);
grp.sync();
}
return true;
}
}
......
......@@ -27,6 +27,7 @@ class WebSslInfo;
class WebEnginePart;
class KPasswdServerClient;
class WebEngineWallet;
class WebEnginePartCertificateErrorDlg;
class WebEnginePage : public QWebEnginePage
{
......@@ -59,12 +60,12 @@ public:
/**
* @brief Tells the page that the part has requested to load the given URL
*
*
* @note Calling this function doesn't cause the page to be loaded: you still need to call load() to do so.
* @see m_urlLoadedByPart
* @see m_urlRequestedByApp
* @param url the requested URL
*/
void setLoadUrlCalledByPart(const QUrl &url){m_urlLoadedByPart = url;}
void markUrlAsRequestedByApp(const QUrl &url){m_urlRequestedByApp = url;}
Q_SIGNALS:
/**
......@@ -72,7 +73,7 @@ Q_SIGNALS:
* request.
*/
void loadAborted(const QUrl &url);
void leavingPage(QWebEnginePage::NavigationType type);
protected:
......@@ -102,22 +103,21 @@ protected:
/**
* @brief Override of `QWebEnginePage::certificateError`
*
* If the error is overridable, asks the user whether to ignore the error or not and returns `true` or `false` accordingly.
* If the error is not overridable, it always returns `false`.
*
*
* If the error is overridable, it first checks whether the user has already choosen to permanently ignore the error, in which case it returns `true`.
* If the user hasn't made such a choice, the error is deferred and a WebEnginePartCertificateErrorDlg is shown. The result is handled by handleCertificateError.
*
* @internal
* A problem arises if the certificate error happens while loading a page requested by WebEnginePart::load() (rather than from the
* user's interaction with the WebEnginePage itself). The problem is that when WebEnginePart::load() is called, any certificate error
* will have already been caught by the `KParts` mechanism and the user will already have been asked about it. so it doesn't make sense
* to ask him again. To avoid doing so, this function checks m_urlLoadedByPart: if it is the same url as the one the certificate
* refers to, `true` is returned (and m_urlLoadedByPart is reset).
* @endinternal
*
* @param ce the certificate error
* @return `true` if the error can be ignored and `false` otherwise
* A problem arises if the certificate error happens while loading a page opened from a part which is not a WebEnginePart (this also includes the case when the
* URL is entered in the location bar when the part is active). In this case, the URL is first loaded by KIO, then by WebEnginePart. If there's a certificate error,
* it will be reported twice: by KIO and by the WebEnginePart. The old trick of using `m_urlLoadedByPart` doesn't work anymore, because if the current part is
* a WebEnginePart, WebEnginePart::openUrl will be called, but everything will be handled by WebEnginePart.
*
* @param _ce the certificate error
* @return @b false if the error is not overridable and @true in all other cases. Note that if @b _ce is deferred, according to the documentation for `QWebEngineCertificateError
* the return value is ignored.
*/
bool certificateError(const QWebEngineCertificateError &ce) override;
bool certificateError(const QWebEngineCertificateError &_ce) override;
protected Q_SLOTS:
void slotLoadFinished(bool ok);
......@@ -133,6 +133,17 @@ private:
bool handleMailToUrl (const QUrl& , NavigationType type) const;
void setPageJScriptPolicy(const QUrl& url);
/**
* @brief Function called in response to the user closing the certificate error dialog
*
* Depending on the user's choice, this function will instruct the page to ignore the error or not
* and, if the user chose to forever ignore the error, it'll record this decision in the configuration
* file.
*
* @param dlg the certificate error dialog
*/
void handleCertificateError(WebEnginePartCertificateErrorDlg *dlg);
private:
enum WebEnginePageSecurity { PageUnencrypted, PageEncrypted, PageMixed };
......@@ -146,15 +157,19 @@ private:
WebEngineWallet *m_wallet;
/**
* @brief The last URL that the part requested to be loaded
*
* Before calling `load()`, the part needs to call setLoadUrlCalledByPart() passing the URL which will be loaded. This variable
* will be reset either the first time acceptNavigationRequest() is called with a different URL or when certificateError() is called.
*
* This variable is needed to implement certificateError().
*
* @brief The last URL that the application explicitly asked this part to open
*
* Before calling `load()`, the part needs to call markUrlAsAlreadyProcessedByApp() passing the URL which will be loaded. This variable
* will be reset the first time acceptNavigationRequest() is called with a different URL.
*
* This variable is used by acceptNavigationRequest() to decide how to handle local files: if the argument passed to
* acceptNavigationRequest() is the same as m_urlAlreadyProcessedByApp, it means that the application explicitly asked the part to
* open the URL, so acceptNavigationRequest() does just that. Otherwise, it'll pass emit the KParts::BrowserExtension::openUrlRequest
* signal so that the application can decide how to open the URL.
* @note This mechanism is only used for local files.
*
*/
QUrl m_urlLoadedByPart;
QUrl m_urlRequestedByApp;
};
......@@ -192,7 +207,8 @@ private:
KParts::WindowArgs m_windowArgs;
WebWindowType m_type;
bool m_createNewWindow;
WebEngineWallet* m_wallet;
WebEngineWallet* m_wallet;
};
#endif // WEBENGINEPAGE_H
......@@ -407,6 +407,10 @@ bool WebEnginePart::openUrl(const QUrl &_u)
{
QUrl u (_u);
if (property("urlRequestedByApp").toString() == u.url()) {
page()->markUrlAsRequestedByApp(u);
}
// Ignore empty requests...
if (u.isEmpty())
return false;
......@@ -445,7 +449,6 @@ bool WebEnginePart::openUrl(const QUrl &_u)
// Set URL in KParts before emitting started; konq plugins rely on that.
setUrl(u);
m_doLoadFinishedActions = true;
page()->setLoadUrlCalledByPart(u);
m_webView->loadUrl(u, args, bargs);
return true;
}
......
/*
* This file is part of the KDE project.
*
* Copyright 2021 Stefano Crocco <posta@stefanocrocco.it>
*
* This program 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program 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, see <http://www.gnu.org/licenses/>.
*/
#include "webenginepartcertificateerrordlg.h"
#include "ui_webenginepartcertificateerrordlg.h"
#include <KIOWidgets/KSslCertificateBox>
#include <QAbstractButton>
#include <QPushButton>
WebEnginePartCertificateErrorDlg::WebEnginePartCertificateErrorDlg(const QWebEngineCertificateError &error, QWidget* parent):
QDialog(parent),
m_ui(new Ui::WebEnginePartCertificateErrorDlg), m_error(error), m_choice(UserChoice::DontIgnoreError)
{
m_ui->setupUi(this);
connect(m_ui->buttons, &QDialogButtonBox::clicked, this, &WebEnginePartCertificateErrorDlg::updateUserChoice);
connect(m_ui->showDetails, &QCheckBox::toggled, m_ui->details, &QWidget::setVisible);
connect(m_ui->certificateChain, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &WebEnginePartCertificateErrorDlg::displayCertificate);
m_ui->buttons->button(QDialogButtonBox::No)->setDefault(true);
m_ui->buttons->button(QDialogButtonBox::Yes)->setText(i18nc("Ignore the certificate error for this URL only for now", "Yes, &once"));
m_ui->buttons->button(QDialogButtonBox::YesToAll)->setText(i18nc("Ignore the certificate error for this URL now and in the future", "Yes, &forever"));
m_ui->details->hide();
QString translatedDesc = i18n(m_error.errorDescription().toUtf8());
QString text = i18n("<p>The server failed the authenticity check (%1). The error is:</p><p><tt>%2</tt></p>Do you want to ignore this error?",
m_error.url().host(), translatedDesc);
m_ui->label->setText(text);
for (const QSslCertificate &cert : m_error.certificateChain()) {
m_ui->certificateChain->addItem(cert.subjectDisplayName());
}
}
WebEnginePartCertificateErrorDlg::~WebEnginePartCertificateErrorDlg()
{
}
QWebEngineCertificateError WebEnginePartCertificateErrorDlg::certificateError() const
{
return m_error;
}
WebEnginePartCertificateErrorDlg::UserChoice WebEnginePartCertificateErrorDlg::userChoice() const
{
return m_choice;
}
void WebEnginePartCertificateErrorDlg::displayCertificate(int idx)
{
m_ui->subjectData->setCertificate(m_error.certificateChain().at(idx), KSslCertificateBox::Subject);;
m_ui->issuerData->setCertificate(m_error.certificateChain().at(idx), KSslCertificateBox::Issuer);;
}
void WebEnginePartCertificateErrorDlg::updateUserChoice(QAbstractButton* btn)
{
QDialogButtonBox::StandardButton code = m_ui->buttons->standardButton(btn);
switch (code) {
case QDialogButtonBox::Yes:
m_choice = UserChoice::IgnoreErrorOnce;
break;
case QDialogButtonBox::YesToAll:
m_choice = UserChoice::IgnoreErrorForever;
break;
default:
m_choice = UserChoice::DontIgnoreError;
}
}
/*
* This file is part of the KDE project.
*
* Copyright 2021 Stefano Crocco <posta@stefanocrocco.it>
*
* This program 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef WEBENGINEPARTCERTIFICATEERRORDLG_H
#define WEBENGINEPARTCERTIFICATEERRORDLG_H
#include <QDialog>
#include <QWebEngineCertificateError>
class QAbstractButton;
namespace Ui
{
class WebEnginePartCertificateErrorDlg;
}
/**
* @todo write docs
*/
class WebEnginePartCertificateErrorDlg : public QDialog
{
Q_OBJECT
public:
WebEnginePartCertificateErrorDlg(const QWebEngineCertificateError& error, QWidget* parent);
~WebEnginePartCertificateErrorDlg();
enum class UserChoice{DontIgnoreError, IgnoreErrorOnce, IgnoreErrorForever};
UserChoice userChoice() const;
QWebEngineCertificateError certificateError() const;
private slots:
void displayCertificate(int idx);
void updateUserChoice(QAbstractButton *btn);
private:
Ui::WebEnginePartCertificateErrorDlg *m_ui;
QWebEngineCertificateError m_error;
UserChoice m_choice;
};
#endif // WEBENGINEPARTCERTIFICATEERRORDLG_H
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WebEnginePartCertificateErrorDlg</class>
<widget class="QDialog" name="WebEnginePartCertificateErrorDlg">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>758</width>
<height>232</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showDetails">
<property name="text">
<string>&amp;Show details</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttons">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::No|QDialogButtonBox::Yes|QDialogButtonBox::YesToAll</set>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="details">
<property name="title">
<string>Certificate details</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QComboBox" name="certificateChain"/>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="subjectTab">
<attribute name="title">
<string>&amp;Subject</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="KSslCertificateBox" name="subjectData" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="issuerTab">
<attribute name="title">
<string>&amp;Issuer</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="KSslCertificateBox" name="issuerData" native="true"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KSslCertificateBox</class>
<extends>QWidget</extends>
<header location="global">KIOWidgets/KSslCertificateBox</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttons</sender>
<signal>accepted()</signal>
<receiver>WebEnginePartCertificateErrorDlg</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttons</sender>
<signal>rejected()</signal>
<receiver>WebEnginePartCertificateErrorDlg</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment