kde-apps/libkgapi: Fix 2FA, add USE=nls and missing DEPEND
authorAndreas Sturmlechner <asturm@gentoo.org>
Thu, 27 Apr 2017 17:52:40 +0000 (19:52 +0200)
committerAndreas Sturmlechner <asturm@gentoo.org>
Thu, 27 Apr 2017 18:14:43 +0000 (20:14 +0200)
Package-Manager: Portage-2.3.3, Repoman-2.3.1

kde-apps/libkgapi/files/libkgapi-17.04.0-auth1.patch [new file with mode: 0644]
kde-apps/libkgapi/files/libkgapi-17.04.0-auth2.patch [new file with mode: 0644]
kde-apps/libkgapi/files/libkgapi-17.04.0-auth3.patch [new file with mode: 0644]
kde-apps/libkgapi/libkgapi-17.04.0-r1.ebuild [new file with mode: 0644]

diff --git a/kde-apps/libkgapi/files/libkgapi-17.04.0-auth1.patch b/kde-apps/libkgapi/files/libkgapi-17.04.0-auth1.patch
new file mode 100644 (file)
index 0000000..eb42646
--- /dev/null
@@ -0,0 +1,168 @@
+From 68b89bce22d0da234345ccffb869ae6863592624 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Daniel=20Vr=C3=A1til?= <dvratil@kde.org>
+Date: Thu, 27 Apr 2017 17:22:27 +0200
+Subject: [PATCH 1/3] Auth: Adapt to changes in Google OAuth token retrieval
+ process
+
+URLs and HTML code have changed a bit, which breaks authentication. Hopefully
+they won't change it too often in the future.
+---
+ src/core/ui/authwidget_p.cpp | 109 ++++++++++++++++++++++++-------------------
+ src/core/ui/authwidget_p.h   |   6 +++
+ 2 files changed, 67 insertions(+), 48 deletions(-)
+
+diff --git a/src/core/ui/authwidget_p.cpp b/src/core/ui/authwidget_p.cpp
+index 6de33f5..75d38cd 100644
+--- a/src/core/ui/authwidget_p.cpp
++++ b/src/core/ui/authwidget_p.cpp
+@@ -112,11 +112,35 @@ void AuthWidgetPrivate::emitError(const enum Error errCode, const QString& msg)
+ void AuthWidgetPrivate::webviewUrlChanged(const QUrl &url)
+ {
+-    qCDebug(KGAPIDebug) << url;
++    qCDebug(KGAPIDebug) << "URLChange:" << url;
+-    /* Access token here - hide browser and tell user to wait until we
+-     * finish the authentication process ourselves */
+-    if (url.host() == QLatin1String("accounts.google.com") && url.path() == QLatin1String("/o/oauth2/approval")) {
++    if (!isGoogleHost(url)) {
++        return;
++    }
++
++    // Username and password inputs are loaded dynamically, so we only get
++    // urlChanged, but not urlFinished.
++    if (isUsernameFrame(url)) {
++#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
++        if (!username.isEmpty()) {
++            webview->page()->runJavaScript(QStringLiteral("document.getElementById(\"identifierId\").value = \"%1\";").arg(username));
++        }
++#endif
++    } else if (isPasswordFrame(url)) {
++#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
++        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));
++        }
++#endif
++    } else if (isTokenPage(url)) {
++        /* Access token here - hide browser and tell user to wait until we
++         * finish the authentication process ourselves */
+         webview->setVisible(false);
+         progressbar->setVisible(false);
+         label->setVisible(true);
+@@ -131,57 +155,46 @@ void AuthWidgetPrivate::webviewFinished(bool ok)
+         qCWarning(KGAPIDebug) << "Failed to load" << webview->url();
+     }
+-    QUrl url = webview->url();
+-    qCDebug(KGAPIDebug) << url;
+-
+-    if (url.host() == QLatin1String("accounts.google.com") && url.path() == QLatin1String("/ServiceLogin")) {
+-        if (username.isEmpty() && password.isEmpty()) {
+-            return;
+-        }
+-
+-#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
+-        const auto js = QStringLiteral("document.getElementById(\"%1\").value = \"%2\";");
+-        if (!username.isEmpty()) {
+-            webview->page()->runJavaScript(js.arg(QStringLiteral("Email"), username));
+-        }
+-
+-        if (!password.isEmpty()) {
+-            webview->page()->runJavaScript(js.arg(QStringLiteral("Passwd"), password));
+-        }
+-#endif
++    const QUrl url = webview->url();
++    qCDebug(KGAPIDebug) << "URLFinished:" << url;
++    if (!isGoogleHost(url)) {
+         return;
+     }
+-    if (url.host() == QLatin1String("accounts.google.com") && url.path() == QLatin1String("/o/oauth2/approval")) {
+-        QString title = webview->title();
+-        QString token;
+-
+-        if (title.startsWith(QLatin1String("success"), Qt::CaseInsensitive)) {
+-            int pos = title.indexOf(QLatin1String("code="));
+-            /* Skip the 'code=' string as well */
+-            token = title.mid (pos + 5);
++    if (isTokenPage(url)) {
++        const auto token = url.queryItemValue(QStringLiteral("approvalCode"));
++        if (!token.isEmpty()) {
++            qCDebug(KGAPIDebug) << "Got token: " << token;
++            auto fetch = new KGAPI2::NewTokensFetchJob(token, apiKey, secretKey);
++            connect(fetch, &Job::finished, this, &AuthWidgetPrivate::tokensReceived);
+         } else {
+-            webview->page()->toHtml([title](const QString &html) {
+-                qCDebug(KGAPIDebug) << "Parsing token page failed. Title:" << title;
+-                qCDebug(KGAPIDebug) << html;
+-            });
++#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
++            qCWarning(KGAPIDebug) << "Failed to parse token from URL, peaking into HTML...";
++            webview->page()->runJavaScript(
++                QStringLiteral("document.getElementById(\"code\").value;"),
++                [this](const QVariant &result) {
++                    const auto token = result.toString();
++                    if (token.isEmpty()) {
++                        qCWarning(KGAPIDebug) << "Peaked into HTML, but cound not find token :(";
++                        webview->page()->toHtml([](const QString &html) {
++                            qCDebug(KGAPIDebug) << "Parsing token page failed";
++                            qCDebug(KGAPIDebug) << html;
++                        });
++                        emitError(AuthError, tr("Parsing token page failed."));
++                        return;
++                    }
++                    qCDebug(KGAPIDebug) << "Peaked into HTML and found token: " << token;
++                    auto fetch = new KGAPI2::NewTokensFetchJob(token, apiKey, secretKey);
++                    connect(fetch, &Job::finished, this, &AuthWidgetPrivate::tokensReceived);
++                });
++#else
++            qCWarning(KGAPIDebug) << "Failed to parse token from URL!";
+             emitError(AuthError, tr("Parsing token page failed."));
+-            return;
+-        }
+-
+-        if (token.isEmpty()) {
+-            webview->page()->toHtml([](const QString &html) {
+-                qCDebug(KGAPIDebug) << "Failed to obtain token.";
+-                qCDebug(KGAPIRaw) << html;
+-            });
+-            emitError(AuthError, tr("Failed to obtain token."));
+-            return;
++#endif
+         }
+-
+-        KGAPI2::NewTokensFetchJob *fetchJob = new KGAPI2::NewTokensFetchJob(token, apiKey, secretKey);
+-        connect(fetchJob, &Job::finished,
+-                this, &AuthWidgetPrivate::tokensReceived);
++    } else {
++        //qCDebug(KGAPIDebug) << "Unhandled page:" << url.host() << ", " << url.path();
+     }
+ }
+diff --git a/src/core/ui/authwidget_p.h b/src/core/ui/authwidget_p.h
+index 673b0cb..9c488be 100644
+--- a/src/core/ui/authwidget_p.h
++++ b/src/core/ui/authwidget_p.h
+@@ -82,6 +82,12 @@ class Q_DECL_HIDDEN AuthWidgetPrivate: public QObject {
+     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"); }
++    bool isTokenPage(const QUrl &url) { return url.path() == QLatin1String("/o/oauth2/approval/v2"); }
++
+     AuthWidget *q;
+     friend class AuthWidget;
+-- 
+2.12.2
+
diff --git a/kde-apps/libkgapi/files/libkgapi-17.04.0-auth2.patch b/kde-apps/libkgapi/files/libkgapi-17.04.0-auth2.patch
new file mode 100644 (file)
index 0000000..cba162a
--- /dev/null
@@ -0,0 +1,36 @@
+From 5a20c7494f48da93914f50bbb54423ef540ef998 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Daniel=20Vr=C3=A1til?= <dvratil@kde.org>
+Date: Thu, 27 Apr 2017 17:24:03 +0200
+Subject: [PATCH 2/3] Auth: don't store cookies
+
+Don't store WebEngine cookies, otherwise Google will display the account
+that user signed in with the last time, which may not be desirable.
+---
+ src/core/ui/authwidget_p.cpp | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/src/core/ui/authwidget_p.cpp b/src/core/ui/authwidget_p.cpp
+index 75d38cd..a51f4a9 100644
+--- a/src/core/ui/authwidget_p.cpp
++++ b/src/core/ui/authwidget_p.cpp
+@@ -26,6 +26,7 @@
+ #include "../../debug.h"
+ #include <QWebEngineView>
++#include <QWebEngineProfile>
+ #include <QNetworkReply>
+ #include <QContextMenuEvent>
+@@ -36,6 +37,9 @@ using namespace KGAPI2;
+ WebView::WebView(QWidget *parent)
+     : QWebEngineView(parent)
+ {
++    // Don't store cookies, so that subsequent invocations of AuthJob won't remember
++    // the previous accounts.
++    QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies);
+ }
+-- 
+2.12.2
+
diff --git a/kde-apps/libkgapi/files/libkgapi-17.04.0-auth3.patch b/kde-apps/libkgapi/files/libkgapi-17.04.0-auth3.patch
new file mode 100644 (file)
index 0000000..61d3b30
--- /dev/null
@@ -0,0 +1,310 @@
+From 7b0934611ef72fb7e7c405813a1d2bb8b944dadc Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Daniel=20Vr=C3=A1til?= <dvratil@kde.org>
+Date: Thu, 27 Apr 2017 18:41:45 +0200
+Subject: [PATCH 3/3] Auth: add URL bar and SSL indicator to the authentication
+ page
+
+To increase trust-worthiness of the authentication dialog we now display
+the URL and an SSL indicator above the webview.
+---
+ src/core/ui/authwidget.cpp   |   2 +
+ src/core/ui/authwidget_p.cpp | 137 +++++++++++++++++++++++++++++++++++++++----
+ src/core/ui/authwidget_p.h   |  25 ++++----
+ 3 files changed, 136 insertions(+), 28 deletions(-)
+
+diff --git a/src/core/ui/authwidget.cpp b/src/core/ui/authwidget.cpp
+index 18d2106..ac09b63 100644
+--- a/src/core/ui/authwidget.cpp
++++ b/src/core/ui/authwidget.cpp
+@@ -107,6 +107,8 @@ void AuthWidget::authenticate()
+     qCDebug(KGAPIRaw) << "Requesting new token:" << url;
++    d->sslIndicator->setVisible(true);
++    d->urlEdit->setVisible(true);
+     d->webview->setVisible(true);
+     if (d->showProgressBar) {
+         d->progressbar->setVisible(true);
+diff --git a/src/core/ui/authwidget_p.cpp b/src/core/ui/authwidget_p.cpp
+index a51f4a9..f732935 100644
+--- a/src/core/ui/authwidget_p.cpp
++++ b/src/core/ui/authwidget_p.cpp
+@@ -25,35 +25,79 @@
+ #include "private/newtokensfetchjob_p.h"
+ #include "../../debug.h"
+-#include <QWebEngineView>
+ #include <QWebEngineProfile>
+ #include <QNetworkReply>
+ #include <QContextMenuEvent>
++#include <QVBoxLayout>
++#include <QLabel>
++#include <QTimer>
++#include <QMessageBox>
++
+ #include <QDateTime>
+ using namespace KGAPI2;
+-WebView::WebView(QWidget *parent)
+-    : QWebEngineView(parent)
++namespace
+ {
+-    // Don't store cookies, so that subsequent invocations of AuthJob won't remember
+-    // the previous accounts.
+-    QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies);
+-}
+-
+-WebView::~WebView()
++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) Q_DECL_OVERRIDE
++    {
++        // No menu
++        e->accept();
++    }
++};
+-void WebView::contextMenuEvent(QContextMenuEvent *e)
++class WebPage : public QWebEnginePage
+ {
+-    // No menu
+-    e->accept();
++    Q_OBJECT
++public:
++    explicit WebPage(QObject *parent = nullptr)
++        : QWebEnginePage(parent)
++        , mLastError(nullptr)
++    {
++    }
++
++    QWebEngineCertificateError *lastCertificateError() const
++    {
++        return mLastError;
++    }
++
++    bool certificateError(const QWebEngineCertificateError &err) Q_DECL_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),
+@@ -67,6 +111,15 @@ 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);
+@@ -79,6 +132,26 @@ void AuthWidgetPrivate::setupUi()
+     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<WebPage*>(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);
+@@ -87,6 +160,13 @@ void AuthWidgetPrivate::setupUi()
+     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);
+@@ -104,6 +184,8 @@ void AuthWidgetPrivate::setProgress(AuthWidget::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);
+@@ -118,10 +200,33 @@ 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<WebPage*>(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)) {
+@@ -145,6 +250,8 @@ void AuthWidgetPrivate::webviewUrlChanged(const QUrl &url)
+     } else if (isTokenPage(url)) {
+         /* Access token here - hide browser and tell user to wait until we
+          * finish the authentication process ourselves */
++        sslIndicator->setVisible(false);
++        urlEdit->setVisible(false);
+         webview->setVisible(false);
+         progressbar->setVisible(false);
+         label->setVisible(true);
+@@ -160,6 +267,8 @@ void AuthWidgetPrivate::webviewFinished(bool ok)
+     }
+     const QUrl url = webview->url();
++    urlEdit->setText(url.toDisplayString(QUrl::PrettyDecoded));
++    urlEdit->setCursorPosition(0);
+     qCDebug(KGAPIDebug) << "URLFinished:" << url;
+     if (!isGoogleHost(url)) {
+@@ -238,3 +347,5 @@ void AuthWidgetPrivate::accountInfoReceived(KGAPI2::Job* job)
+     setProgress(AuthWidget::Finished);
+ }
++
++#include "authwidget_p.moc"
+diff --git a/src/core/ui/authwidget_p.h b/src/core/ui/authwidget_p.h
+index 9c488be..78b0e7f 100644
+--- a/src/core/ui/authwidget_p.h
++++ b/src/core/ui/authwidget_p.h
+@@ -26,27 +26,18 @@
+ #include "ui/authwidget.h"
+ #include "types.h"
++#include <QLineEdit>
++#include <QToolButton>
+ #include <QProgressBar>
+-#include <QVBoxLayout>
+ #include <QWebEngineView>
+-#include <QLabel>
++
++class QVBoxLayout;
++class QLabel;
+ namespace KGAPI2 {
+ class Job;
+-class WebView : public QWebEngineView
+-{
+-    Q_OBJECT
+-public:
+-    explicit WebView(QWidget *parent=0);
+-    ~WebView();
+-
+-protected:
+-    void contextMenuEvent(QContextMenuEvent *) Q_DECL_OVERRIDE;
+-};
+-
+-
+ class Q_DECL_HIDDEN AuthWidgetPrivate: public QObject {
+     Q_OBJECT
+@@ -65,9 +56,11 @@ class Q_DECL_HIDDEN AuthWidgetPrivate: public QObject {
+     QString apiKey;
+     QString secretKey;
++    QToolButton *sslIndicator;
++    QLineEdit *urlEdit;
+     QProgressBar *progressbar;
+     QVBoxLayout *vbox;
+-    WebView *webview;
++    QWebEngineView *webview;
+     QLabel *label;
+   private Q_SLOTS:
+@@ -88,6 +81,8 @@ class Q_DECL_HIDDEN AuthWidgetPrivate: public QObject {
+     bool isPasswordFrame(const QUrl &url) { return url.path() == QLatin1String("/signin/v2/challenge/pwd"); }
+     bool isTokenPage(const QUrl &url) { return url.path() == QLatin1String("/o/oauth2/approval/v2"); }
++    void setSslIcon(const QString &icon);
++
+     AuthWidget *q;
+     friend class AuthWidget;
+-- 
+2.12.2
+
diff --git a/kde-apps/libkgapi/libkgapi-17.04.0-r1.ebuild b/kde-apps/libkgapi/libkgapi-17.04.0-r1.ebuild
new file mode 100644 (file)
index 0000000..d48cb19
--- /dev/null
@@ -0,0 +1,36 @@
+# Copyright 1999-2017 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+EAPI=6
+
+KDE_BLOCK_SLOT4="false"
+KDE_TEST="true"
+VIRTUALX_REQUIRED="test"
+inherit kde5
+
+DESCRIPTION="Library for accessing Google calendar and contact resources"
+HOMEPAGE="https://projects.kde.org/projects/extragear/libs/libkgapi"
+
+LICENSE="LGPL-2.1+"
+KEYWORDS="~amd64 ~x86"
+IUSE="nls"
+
+COMMON_DEPEND="
+       $(add_frameworks_dep kio)
+       $(add_frameworks_dep kwindowsystem)
+       $(add_kdeapps_dep kcalcore)
+       $(add_kdeapps_dep kcontacts)
+       $(add_qt_dep qtgui)
+       $(add_qt_dep qtnetwork)
+       $(add_qt_dep qtwebengine)
+       $(add_qt_dep qtwidgets)
+       $(add_qt_dep qtxml)
+"
+DEPEND="${COMMON_DEPEND}
+       nls? ( $(add_qt_dep linguist-tools) )
+"
+RDEPEND="${COMMON_DEPEND}
+       !kde-apps/kdepim-l10n
+"
+
+PATCHES=( "${FILESDIR}"/${P}-auth{1,2,3}.patch )