diff --git a/src/core/private/newtokensfetchjob.cpp b/src/core/private/newtokensfetchjob.cpp --- a/src/core/private/newtokensfetchjob.cpp +++ b/src/core/private/newtokensfetchjob.cpp @@ -43,19 +43,21 @@ QString tmpToken; QString apiKey; QString secretKey; + int localPort; QString accessToken; QString refreshToken; qulonglong expiresIn; }; -NewTokensFetchJob::NewTokensFetchJob(const QString &tmpToken, const QString &apiKey, const QString &secretKey, QObject *parent): +NewTokensFetchJob::NewTokensFetchJob(const QString &tmpToken, const QString &apiKey, const QString &secretKey, int localPort, QObject *parent): Job(parent), d(new Private) { d->tmpToken = tmpToken; d->apiKey = apiKey; d->secretKey = secretKey; + d->localPort = localPort; } NewTokensFetchJob::~NewTokensFetchJob() @@ -104,7 +106,7 @@ params.addQueryItem(QStringLiteral("client_id"), d->apiKey); params.addQueryItem(QStringLiteral("client_secret"), d->secretKey); params.addQueryItem(QStringLiteral("code"), d->tmpToken); - params.addQueryItem(QStringLiteral("redirect_uri"), QStringLiteral("urn:ietf:wg:oauth:2.0:oob")); + params.addQueryItem(QStringLiteral("redirect_uri"), QStringLiteral("http://127.0.0.1:%1").arg(d->localPort)); // we need to use the same URL as in AuthWidget params.addQueryItem(QStringLiteral("grant_type"), QStringLiteral("authorization_code")); enqueueRequest(request, params.encodedQuery()); diff --git a/src/core/private/newtokensfetchjob_p.h b/src/core/private/newtokensfetchjob_p.h --- a/src/core/private/newtokensfetchjob_p.h +++ b/src/core/private/newtokensfetchjob_p.h @@ -38,7 +38,7 @@ Q_OBJECT public: - explicit NewTokensFetchJob(const QString &tmpToken, const QString &apiKey, const QString &secretKey, QObject* parent = nullptr); + explicit NewTokensFetchJob(const QString &tmpToken, const QString &apiKey, const QString &secretKey, int localPort, QObject* parent = nullptr); virtual ~NewTokensFetchJob(); QString accessToken() const; diff --git a/src/core/ui/authwidget.cpp b/src/core/ui/authwidget.cpp --- a/src/core/ui/authwidget.cpp +++ b/src/core/ui/authwidget.cpp @@ -22,6 +22,8 @@ #include "authwidget_p.h" #include "../../debug.h" +#include +#include using namespace KGAPI2; @@ -99,9 +101,26 @@ scopes << scope.toString(); } + d->server = new QTcpServer(this); + if (!d->server->listen(QHostAddress::LocalHost)) { + Q_EMIT error(InvalidAccount, QStringLiteral("Could not start oauth http server")); + return; + } + connect(d->server, &QTcpServer::acceptError, d, &AuthWidgetPrivate::socketError); + d->serverPort = d->server->serverPort(); + connect(d->server, &QTcpServer::newConnection, [&]() { + d->connection = d->server->nextPendingConnection(); + d->connection->setParent(this); + connect(d->connection, static_cast + (&QAbstractSocket::error), d, &AuthWidgetPrivate::socketError); + connect(d->connection, &QTcpSocket::readyRead, d, &AuthWidgetPrivate::socketReady); + d->server->close(); + d->server->deleteLater(); + }); + QUrl url(QStringLiteral("https://accounts.google.com/o/oauth2/auth")); url.addQueryItem(QStringLiteral("client_id"), d->apiKey); - url.addQueryItem(QStringLiteral("redirect_uri"), QStringLiteral("urn:ietf:wg:oauth:2.0:oob")); + url.addQueryItem(QStringLiteral("redirect_uri"), QStringLiteral("http://127.0.0.1:%1").arg(d->serverPort)); url.addQueryItem(QStringLiteral("scope"), scopes.join(QStringLiteral(" "))); url.addQueryItem(QStringLiteral("response_type"), QStringLiteral("code")); diff --git a/src/core/ui/authwidget_p.h b/src/core/ui/authwidget_p.h --- a/src/core/ui/authwidget_p.h +++ b/src/core/ui/authwidget_p.h @@ -30,6 +30,7 @@ #include #include #include +#include class QVBoxLayout; class QLabel; @@ -63,11 +64,16 @@ 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); @@ -79,7 +85,6 @@ 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/approvalnativeapp"); } void setSslIcon(const QString &icon); diff --git a/src/core/ui/authwidget_p.cpp b/src/core/ui/authwidget_p.cpp --- a/src/core/ui/authwidget_p.cpp +++ b/src/core/ui/authwidget_p.cpp @@ -28,10 +28,12 @@ #include #include +#include #include #include #include #include +#include #include @@ -169,7 +171,6 @@ 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) @@ -242,63 +243,57 @@ " }" "}").arg(password)); } - } 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); - - setProgress(AuthWidget::TokensRetrieval); } } -void AuthWidgetPrivate::webviewFinished(bool ok) +void AuthWidgetPrivate::socketError(QAbstractSocket::SocketError socketError) { - if (!ok) { - qCWarning(KGAPIDebug) << "Failed to load" << webview->url(); - } + if (connection) + connection->deleteLater(); + qCDebug(KGAPIDebug) << QStringLiteral("Socket error when recieving response: %1").arg(socketError); + emitError(InvalidResponse, QStringLiteral("Error recieving response: %1").arg(socketError)); +} - const QUrl url = webview->url(); - urlEdit->setText(url.toDisplayString(QUrl::PrettyDecoded)); - urlEdit->setCursorPosition(0); - qCDebug(KGAPIDebug) << "URLFinished:" << url; +void AuthWidgetPrivate::socketReady() +{ + Q_ASSERT(connection); + const QByteArray data = connection->readLine(); + connection->deleteLater(); + qCDebug(KGAPIDebug) << QStringLiteral("Got connection on socket"); + webview->stop(); - if (!isGoogleHost(url)) { + 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; } - 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); + //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 { - 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); - }); + qCDebug(KGAPIDebug) << QStringLiteral("Could not extract token from HTTP answer"); + emitError(InvalidResponse, QStringLiteral("Could not extract token from HTTP answer")); } - } else { - //qCDebug(KGAPIDebug) << "Unhandled page:" << url.host() << ", " << url.path(); + 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)