diff --git a/src/core/ui/authwidget_p.cpp b/src/core/ui/authwidget_p.cpp index ccf4875..1256f0c 100644 --- a/src/core/ui/authwidget_p.cpp +++ b/src/core/ui/authwidget_p.cpp @@ -1,336 +1,349 @@ /* Copyright 2012, 2013 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "authwidget_p.h" #include "account.h" #include "accountinfo/accountinfo.h" #include "accountinfo/accountinfofetchjob.h" #include "private/newtokensfetchjob_p.h" #include "../../debug.h" #include #include #include #include #include #include #include #include #include using namespace KGAPI2; namespace { class WebView : public QWebEngineView { Q_OBJECT public: explicit WebView(QWidget *parent = nullptr) : QWebEngineView(parent) { // Don't store cookies, so that subsequent invocations of AuthJob won't remember // the previous accounts. QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); } void contextMenuEvent(QContextMenuEvent *e) override { // No menu e->accept(); } }; class WebPage : public QWebEnginePage { Q_OBJECT public: explicit WebPage(QObject *parent = nullptr) : QWebEnginePage(parent) , mLastError(nullptr) { } QWebEngineCertificateError *lastCertificateError() const { return mLastError; } bool certificateError(const QWebEngineCertificateError &err) override { if (mLastError) { delete mLastError; } mLastError = new QWebEngineCertificateError(err.error(), err.url(), err.isOverridable(), err.errorDescription()); Q_EMIT sslError(); return false; // don't let it through } Q_SIGNALS: void sslError(); private: QWebEngineCertificateError *mLastError; }; } AuthWidgetPrivate::AuthWidgetPrivate(AuthWidget *parent): QObject(), showProgressBar(true), progress(AuthWidget::None), q(parent) { setupUi(); } AuthWidgetPrivate::~AuthWidgetPrivate() { } void AuthWidgetPrivate::setSslIcon(const QString &iconName) { // FIXME: workaround for silly Breeze icons: the small 22x22 icons are // monochromatic, which is absolutely useless since we are trying to security // information here, so instead we force use the bigger 48x48 icons which // have colors and downscale them sslIndicator->setIcon(QIcon::fromTheme(iconName).pixmap(48)); } void AuthWidgetPrivate::setupUi() { vbox = new QVBoxLayout(q); q->setLayout(vbox); label = new QLabel(q); label->setText(QLatin1String("") % tr("Authorizing token. This should take just a moment...") % QLatin1String("")); label->setWordWrap(true); label->setAlignment(Qt::AlignCenter); label->setVisible(false); vbox->addWidget(label); auto hbox = new QHBoxLayout; hbox->setSpacing(0); sslIndicator = new QToolButton(q); connect(sslIndicator, &QToolButton::clicked, this, [this]() { auto page = qobject_cast(webview->page()); if (auto err = page->lastCertificateError()) { QMessageBox msg; msg.setIconPixmap(QIcon::fromTheme(QStringLiteral("security-low")).pixmap(64)); msg.setText(err->errorDescription()); msg.addButton(QMessageBox::Ok); msg.exec(); } }); hbox->addWidget(sslIndicator); urlEdit = new QLineEdit(q); urlEdit->setReadOnly(true); hbox->addWidget(urlEdit); vbox->addLayout(hbox); progressbar = new QProgressBar(q); progressbar->setMinimum(0); progressbar->setMaximum(100); progressbar->setValue(0); vbox->addWidget(progressbar); webview = new WebView(q); auto webpage = new WebPage(webview); connect(webpage, &WebPage::sslError, this, [this]() { setSslIcon(QStringLiteral("security-low")); }); webview->setPage(webpage); vbox->addWidget(webview); connect(webview, &QWebEngineView::loadProgress, progressbar, &QProgressBar::setValue); connect(webview, &QWebEngineView::urlChanged, this, &AuthWidgetPrivate::webviewUrlChanged); + connect(webview, &QWebEngineView::loadFinished, this, &AuthWidgetPrivate::webviewFinished); } void AuthWidgetPrivate::setProgress(AuthWidget::Progress progress) { qCDebug(KGAPIDebug) << progress; this->progress = progress; Q_EMIT q->progress(progress); } void AuthWidgetPrivate::emitError(const enum Error errCode, const QString& msg) { label->setVisible(true); sslIndicator->setVisible(false); urlEdit->setVisible(false); webview->setVisible(false); progressbar->setVisible(false); label->setText(QLatin1String("") % msg % QLatin1String("")); Q_EMIT q->error(errCode, msg); setProgress(AuthWidget::Error); } void AuthWidgetPrivate::webviewUrlChanged(const QUrl &url) { qCDebug(KGAPIDebug) << "URLChange:" << url; // Whoa! That should not happen! if (url.scheme() != QLatin1String("https")) { QTimer::singleShot(0, this, [this, url]() { QUrl sslUrl = url; sslUrl.setScheme(QStringLiteral("https")); webview->setUrl(sslUrl); }); return; } if (!isGoogleHost(url)) { // We handled SSL above, so we are secure. We are however outside of // accounts.google.com, which is a little suspicious in context of this class setSslIcon(QStringLiteral("security-medium")); return; } if (qobject_cast(webview->page())->lastCertificateError()) { setSslIcon(QStringLiteral("security-low")); } else { // We have no way of obtaining current SSL certifiace from QWebEngine, but we // handled SSL and accounts.google.com cases above and QWebEngine did not report // any SSL error to us, so we can assume we are safe. setSslIcon(QStringLiteral("security-high")); } // Username and password inputs are loaded dynamically, so we only get // urlChanged, but not urlFinished. if (isUsernameFrame(url)) { if (!username.isEmpty()) { webview->page()->runJavaScript(QStringLiteral("document.getElementById(\"identifierId\").value = \"%1\";").arg(username)); } } else if (isPasswordFrame(url)) { if (!password.isEmpty()) { webview->page()->runJavaScript(QStringLiteral("var elems = document.getElementsByTagName(\"input\");" "for (var i = 0; i < elems.length; i++) {" " if (elems[i].type == \"password\" && elems[i].name == \"password\") {" " elems[i].value = \"%1\";" " break;" " }" "}").arg(password)); } } } +void AuthWidgetPrivate::webviewFinished(bool ok) +{ + if (!ok) { + qCWarning(KGAPIDebug) << "Failed to load" << webview->url(); + } + + const QUrl url = webview->url(); + urlEdit->setText(url.toDisplayString(QUrl::PrettyDecoded)); + urlEdit->setCursorPosition(0); + qCDebug(KGAPIDebug) << "URLFinished:" << url; +} + void AuthWidgetPrivate::socketError(QAbstractSocket::SocketError socketError) { if (connection) connection->deleteLater(); qCDebug(KGAPIDebug) << QStringLiteral("Socket error when recieving response: %1").arg(socketError); emitError(InvalidResponse, QStringLiteral("Error recieving response: %1").arg(socketError)); } void AuthWidgetPrivate::socketReady() { Q_ASSERT(connection); const QByteArray data = connection->readLine(); connection->deleteLater(); qCDebug(KGAPIDebug) << QStringLiteral("Got connection on socket"); webview->stop(); sslIndicator->setVisible(false); urlEdit->setVisible(false); webview->setVisible(false); progressbar->setVisible(false); label->setVisible(true); const auto line = data.split(' '); if (line.size() != 3 || line.at(0) != QByteArray("GET") || !line.at(2).startsWith(QByteArray("HTTP/1.1"))) { qCDebug(KGAPIDebug) << QStringLiteral("Token response invalid"); emitError(InvalidResponse, QStringLiteral("Token response invalid")); return; } //qCDebug(KGAPIRaw) << "Recieving data on socket: " << data; const QUrl url(QString::fromLatin1(line.at(1))); const QUrlQuery query(url.query()); const QString code = query.queryItemValue(QStringLiteral("code")); if (code.isEmpty()) { const QString error = query.queryItemValue(QStringLiteral("error")); if (!error.isEmpty()) { emitError(UnknownError, error); qCDebug(KGAPIDebug) << error; } else { qCDebug(KGAPIDebug) << QStringLiteral("Could not extract token from HTTP answer"); emitError(InvalidResponse, QStringLiteral("Could not extract token from HTTP answer")); } return; } Q_ASSERT(serverPort != -1); auto fetch = new KGAPI2::NewTokensFetchJob(code, apiKey, secretKey, serverPort); connect(fetch, &Job::finished, this, &AuthWidgetPrivate::tokensReceived); } void AuthWidgetPrivate::tokensReceived(KGAPI2::Job* job) { KGAPI2::NewTokensFetchJob *tokensFetchJob = qobject_cast(job); account->setAccessToken(tokensFetchJob->accessToken()); account->setRefreshToken(tokensFetchJob->refreshToken()); account->setExpireDateTime(QDateTime::currentDateTime().addSecs(tokensFetchJob->expiresIn())); tokensFetchJob->deleteLater(); KGAPI2::AccountInfoFetchJob *fetchJob = new KGAPI2::AccountInfoFetchJob(account, this); connect(fetchJob, &Job::finished, this, &AuthWidgetPrivate::accountInfoReceived); qCDebug(KGAPIDebug) << "Requesting AccountInfo"; } void AuthWidgetPrivate::accountInfoReceived(KGAPI2::Job* job) { if (job->error()) { qCDebug(KGAPIDebug) << "Error when retrieving AccountInfo:" << job->errorString(); emitError((enum Error) job->error(), job->errorString()); return; } KGAPI2::ObjectsList objects = qobject_cast(job)->items(); Q_ASSERT(!objects.isEmpty()); KGAPI2::AccountInfoPtr accountInfo = objects.first().staticCast(); account->setAccountName(accountInfo->email()); job->deleteLater(); Q_EMIT q->authenticated(account); setProgress(AuthWidget::Finished); } #include "authwidget_p.moc" diff --git a/src/core/ui/authwidget_p.h b/src/core/ui/authwidget_p.h index fcc447a..b568603 100644 --- a/src/core/ui/authwidget_p.h +++ b/src/core/ui/authwidget_p.h @@ -1,99 +1,100 @@ /* Copyright 2012 Dan Vratil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef LIBKGAPI_AUTHWIDGET_P_H #define LIBKGAPI_AUTHWIDGET_P_H #include #include "ui/authwidget.h" #include "types.h" #include #include #include #include #include class QVBoxLayout; class QLabel; namespace KGAPI2 { class Job; class Q_DECL_HIDDEN AuthWidgetPrivate: public QObject { Q_OBJECT public: explicit AuthWidgetPrivate(AuthWidget *parent); virtual ~AuthWidgetPrivate(); bool showProgressBar; QString username; QString password; AccountPtr account; AuthWidget::Progress progress; QString apiKey; QString secretKey; QToolButton *sslIndicator; QLineEdit *urlEdit; QProgressBar *progressbar; QVBoxLayout *vbox; QWebEngineView *webview; QLabel *label; QTcpServer *server = nullptr; int serverPort = -1; QTcpSocket *connection = nullptr; private Q_SLOTS: void emitError(const KGAPI2::Error errCode, const QString &msg); void webviewUrlChanged(const QUrl &url); + void webviewFinished(bool ok); void socketReady(); void socketError(QAbstractSocket::SocketError error); void tokensReceived(KGAPI2::Job *job); void accountInfoReceived(KGAPI2::Job *job); private: void setupUi(); void setProgress(AuthWidget::Progress progress); bool isGoogleHost(const QUrl &url) const { return url.host() == QLatin1String("accounts.google.com"); } bool isSigninPage(const QUrl &url) const { return url.path() == QLatin1String("/signin/oauth"); } bool isUsernameFrame(const QUrl &url) { return url.path() == QLatin1String("/signin/oauth/identifier"); } bool isPasswordFrame(const QUrl &url) { return url.path() == QLatin1String("/signin/v2/challenge/pwd"); } void setSslIcon(const QString &icon); AuthWidget *q; friend class AuthWidget; }; } // namespace KGAPI2 #endif // LIBKGAPI_AUTHWIDGET_P_H