Use own oauth implementation with webengine.

This commit is contained in:
Martin Rotter 2017-09-25 12:31:37 +02:00
parent 330d5b6865
commit b84bdb306b
8 changed files with 1301 additions and 918 deletions

File diff suppressed because it is too large Load Diff

52
src/gui/dialogs/oauthlogin.cpp Executable file
View File

@ -0,0 +1,52 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard 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 3 of the License, or
// (at your option) any later version.
//
// RSS Guard 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 RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#include "gui/dialogs/oauthlogin.h"
#include <QUrlQuery>
OAuthLogin::OAuthLogin(QWidget* parent) : QDialog(parent) {
m_ui.setupUi(this);
connect(this, &OAuthLogin::rejected, this, &OAuthLogin::authRejected);
connect(m_ui.m_loginPage, &WebViewer::urlChanged, this, &OAuthLogin::urlChanged);
}
void OAuthLogin::login(const QString& consentPageUrl, const QString& redirect_uri) {
m_redirectUri = redirect_uri;
m_ui.m_loginPage->setUrl(QUrl(consentPageUrl));
exec();
}
void OAuthLogin::urlChanged(QUrl url) {
QString redirected_uri = url.toString();
QUrlQuery query(QUrl(redirected_uri).query());
if (redirected_uri.startsWith(m_redirectUri)) {
if (query.hasQueryItem(QSL("code"))) {
emit authGranted(query.queryItemValue(QSL("code")));
accept();
}
else {
emit authRejected();
reject();
}
}
}

50
src/gui/dialogs/oauthlogin.h Executable file
View File

@ -0,0 +1,50 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard 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 3 of the License, or
// (at your option) any later version.
//
// RSS Guard 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 RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#ifndef OAUTHLOGIN_H
#define OAUTHLOGIN_H
#include <QDialog>
#include "ui_oauthlogin.h"
namespace Ui {
class OAuthLogin;
}
class OAuthLogin : public QDialog {
Q_OBJECT
public:
explicit OAuthLogin(QWidget* parent = 0);
void login(const QString& consentPageUrl, const QString& redirect_uri);
private slots:
void urlChanged(QUrl url);
signals:
void authRejected();
void authGranted(QString authCode);
private:
Ui::OAuthLogin m_ui;
QString m_redirectUri;
};
#endif // OAUTHLOGIN_H

59
src/gui/dialogs/oauthlogin.ui Executable file
View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OAuthLogin</class>
<widget class="QDialog" name="OAuthLogin">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>645</width>
<height>310</height>
</rect>
</property>
<property name="windowTitle">
<string>Access authorization to service is requested</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="WebViewer" name="m_loginPage" native="true"/>
</item>
<item>
<widget class="QDialogButtonBox" name="m_buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>WebViewer</class>
<extends>QWidget</extends>
<header>webviewer.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>m_buttonBox</sender>
<signal>rejected()</signal>
<receiver>OAuthLogin</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>

173
src/network-web/oauth2service.cpp Executable file
View File

@ -0,0 +1,173 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard 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 3 of the License, or
// (at your option) any later version.
//
// RSS Guard 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 RSS Guard. If not, see <http://www.gnu.org/licenses/>.
////////////////////////////////////////////////////////////////////////////////
// //
// This file is part of QOAuth2. //
// Copyright (c) 2014 Jacob Dawid <jacob@omg-it.works> //
// //
// QOAuth2 is free software: you can redistribute it and/or modify //
// it under the terms of the GNU Affero General Public License as //
// published by the Free Software Foundation, either version 3 of the //
// License, or (at your option) any later version. //
// //
// QOAuth2 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 Affero General Public License for more details. //
// //
// You should have received a copy of the GNU Affero General Public //
// License along with QOAuth2. //
// If not, see <http://www.gnu.org/licenses/>. //
// //
////////////////////////////////////////////////////////////////////////////////
#include "network-web/oauth2service.h"
#include "definitions/definitions.h"
#include "gui/dialogs/oauthlogin.h"
#include "miscellaneous/application.h"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QNetworkRequest>
OAuth2Service::OAuth2Service(QString authUrl, QString tokenUrl, QString clientId,
QString clientSecret, QString scope, QObject* parent)
: QObject(parent) {
m_redirectUri = QSL("http://localhost");
m_tokenGrantType = QSL("authorization_code");
m_tokenUrl = QUrl(tokenUrl);
m_authUrl = authUrl;
m_clientId = clientId;
m_clientSecret = clientSecret;
m_scope = scope;
connect(&m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(tokenRequestFinished(QNetworkReply*)));
connect(this, &OAuth2Service::authCodeObtained, this, &OAuth2Service::retrieveAccessToken);
}
void OAuth2Service::setBearerHeader(QNetworkRequest& req) {
req.setRawHeader(QString("Authorization").toLocal8Bit(), QString("Bearer %1").arg(m_accessToken).toLocal8Bit());
}
void OAuth2Service::setOAuthTokenGrantType(QString oAuthTokenGrantType) {
m_tokenGrantType = oAuthTokenGrantType;
}
QString OAuth2Service::oAuthTokenGrantType() {
return m_tokenGrantType;
}
void OAuth2Service::retrieveAccessToken(QString auth_code) {
QNetworkRequest networkRequest;
networkRequest.setUrl(m_tokenUrl);
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QString content = QString("client_id=%1&"
"client_secret=%2&"
"code=%3&"
"redirect_uri=%5&"
"grant_type=%4")
.arg(m_clientId)
.arg(m_clientSecret)
.arg(auth_code)
.arg(m_tokenGrantType)
.arg(m_redirectUri);
m_networkManager.post(networkRequest, content.toUtf8());
}
void OAuth2Service::refreshAccessToken(QString refresh_token) {
if (refresh_token.isEmpty()) {
refresh_token = m_refreshToken;
}
QNetworkRequest networkRequest;
networkRequest.setUrl(m_tokenUrl);
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QString content = QString("client_id=%1&"
"client_secret=%2&"
"refresh_token=%3&"
"grant_type=%4")
.arg(m_clientId)
.arg(m_clientSecret)
.arg(refresh_token)
.arg("refresh_token");
m_networkManager.post(networkRequest, content.toUtf8());
}
void OAuth2Service::tokenRequestFinished(QNetworkReply* networkReply) {
QJsonDocument jsonDocument = QJsonDocument::fromJson(networkReply->readAll());
QJsonObject rootObject = jsonDocument.object();
qDebug() << "Token response:";
qDebug() << jsonDocument.toJson();
if(rootObject.keys().contains("error")) {
QString error = rootObject.value("error").toString();
QString error_description = rootObject.value("error_description").toString();
emit tokenRetrieveError(error, error_description);
}
else {
m_accessToken = rootObject.value("access_token").toString();
m_refreshToken = rootObject.value("refresh_token").toString();
// TODO: Start timer to refresh tokens.
emit accessTokenReceived(m_accessToken, m_refreshToken, rootObject.value("expires_in").toInt());
}
networkReply->deleteLater();
}
QString OAuth2Service::refreshToken() const {
return m_refreshToken;
}
void OAuth2Service::setRefreshToken(const QString& refresh_token) {
m_refreshToken = refresh_token;
}
void OAuth2Service::retrieveAuthCode() {
QString auth_url = m_authUrl + QString("?client_id=%1&scope=%2&"
"redirect_uri=%3&response_type=code&state=abcdef").arg(m_clientId,
m_scope,
m_redirectUri);
OAuthLogin login_page(qApp->mainFormWidget());
connect(&login_page, &OAuthLogin::authGranted, this, &OAuth2Service::authCodeObtained);
connect(&login_page, &OAuthLogin::authRejected, this, &OAuth2Service::authFailed);
login_page.login(auth_url, m_redirectUri);
}
QString OAuth2Service::accessToken() const {
return m_accessToken;
}
void OAuth2Service::setAccessToken(const QString& access_token) {
m_accessToken = access_token;
}

97
src/network-web/oauth2service.h Executable file
View File

@ -0,0 +1,97 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2017 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard 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 3 of the License, or
// (at your option) any later version.
//
// RSS Guard 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 RSS Guard. If not, see <http://www.gnu.org/licenses/>.
////////////////////////////////////////////////////////////////////////////////
// //
// This file is part of QOAuth2. //
// Copyright (c) 2014 Jacob Dawid <jacob@omg-it.works> //
// //
// QOAuth2 is free software: you can redistribute it and/or modify //
// it under the terms of the GNU Affero General Public License as //
// published by the Free Software Foundation, either version 3 of the //
// License, or (at your option) any later version. //
// //
// QOAuth2 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 Affero General Public License for more details. //
// //
// You should have received a copy of the GNU Affero General Public //
// License along with QOAuth2. //
// If not, see <http://www.gnu.org/licenses/>. //
// //
////////////////////////////////////////////////////////////////////////////////
#ifndef OAUTH2SERVICE_H
#define OAUTH2SERVICE_H
#include <QObject>
#include "network-web/silentnetworkaccessmanager.h"
class OAuth2Service : public QObject {
Q_OBJECT
public:
explicit OAuth2Service(QString authUrl, QString tokenUrl, QString clientId,
QString clientSecret, QString scope, QObject* parent = 0);
void setBearerHeader(QNetworkRequest& req);
void setOAuthTokenGrantType(QString oAuthTokenGrantType);
QString oAuthTokenGrantType();
QString accessToken() const;
void setAccessToken(const QString& access_token);
QString refreshToken() const;
void setRefreshToken(const QString& refresh_token);
signals:
void accessTokenReceived(QString access_token, QString refresh_token, int expires_in);
void tokenRetrieveError(QString error, QString error_description);
// User failed to authenticate or rejected it.
void authFailed();
// User enabled access.
void authCodeObtained(QString auth_code);
public slots:
void retrieveAuthCode();
void retrieveAccessToken(QString auth_code);
void refreshAccessToken(QString refresh_token = QString());
private slots:
void tokenRequestFinished(QNetworkReply* networkReply);
private:
QString m_accessToken;
QString m_refreshToken;
QString m_redirectUri;
QString m_tokenGrantType;
QString m_clientId;
QString m_clientSecret;
QUrl m_tokenUrl;
QString m_authUrl;
QString m_scope;
SilentNetworkAccessManager m_networkManager;
};
#endif // OAUTH2SERVICE_H

View File

@ -35,14 +35,17 @@
#include <QOAuthHttpServerReplyHandler> #include <QOAuthHttpServerReplyHandler>
#include <QUrl> #include <QUrl>
#include "network-web/oauth2service.h"
InoreaderNetworkFactory::InoreaderNetworkFactory(QObject* parent) : QObject(parent), InoreaderNetworkFactory::InoreaderNetworkFactory(QObject* parent) : QObject(parent),
m_username(QString()), m_refreshToken(QString()), m_batchSize(INOREADER_DEFAULT_BATCH_SIZE), m_username(QString()), m_refreshToken(QString()), m_batchSize(INOREADER_DEFAULT_BATCH_SIZE),
m_oauth2(new QOAuth2AuthorizationCodeFlow(this)) { m_oauth2(new OAuth2Service(INOREADER_OAUTH_AUTH_URL, INOREADER_OAUTH_TOKEN_URL,
INOREADER_OAUTH_CLI_ID, INOREADER_OAUTH_CLI_KEY, "read")) {
initializeOauth(); initializeOauth();
} }
bool InoreaderNetworkFactory::isLoggedIn() const { bool InoreaderNetworkFactory::isLoggedIn() const {
return m_oauth2->expirationAt() > QDateTime::currentDateTime() && m_oauth2->status() == QAbstractOAuth::Status::Granted; return false;
} }
QString InoreaderNetworkFactory::userName() const { QString InoreaderNetworkFactory::userName() const {
@ -58,15 +61,7 @@ void InoreaderNetworkFactory::setBatchSize(int batch_size) {
} }
void InoreaderNetworkFactory::logIn() { void InoreaderNetworkFactory::logIn() {
if (!m_oauth2->expirationAt().isNull() && m_oauth2->retrieveAuthCode();
m_oauth2->expirationAt() <= QDateTime::currentDateTime() &&
!m_refreshToken.isEmpty()) {
// We have some refresh token which expired.
m_oauth2->refreshAccessToken();
}
else {
m_oauth2->grant();
}
} }
void InoreaderNetworkFactory::logInIfNeeded() { void InoreaderNetworkFactory::logInIfNeeded() {
@ -86,53 +81,8 @@ void InoreaderNetworkFactory::tokensReceived(QVariantMap tokens) {
} }
void InoreaderNetworkFactory::initializeOauth() { void InoreaderNetworkFactory::initializeOauth() {
QOAuthHttpServerReplyHandler* oauth_reply_handler = new QOAuthHttpServerReplyHandler(INOREADER_OAUTH_PORT, this); connect(m_oauth2, &OAuth2Service::tokenRetrieveError, [](QString error, QString error_description) {
qApp->showGuiMessage("Authentication error - Inoreader", error_description, QSystemTrayIcon::Critical);
// Full redirect URL is thus "http://localhost:INOREADER_OAUTH_PORT/".
oauth_reply_handler->setCallbackPath(QSL(""));
oauth_reply_handler->setCallbackText(tr("Access to your Inoreader session was granted, you "
"can now <b>close this window and go back to RSS Guard</b>."));
m_oauth2->setAccessTokenUrl(QUrl(INOREADER_OAUTH_TOKEN_URL));
m_oauth2->setAuthorizationUrl(QUrl(INOREADER_OAUTH_AUTH_URL));
m_oauth2->setClientIdentifier(INOREADER_OAUTH_CLI_ID);
m_oauth2->setClientIdentifierSharedKey(INOREADER_OAUTH_CLI_KEY);
m_oauth2->setContentType(QAbstractOAuth::ContentType::Json);
m_oauth2->setNetworkAccessManager(SilentNetworkAccessManager::instance());
m_oauth2->setReplyHandler(oauth_reply_handler);
m_oauth2->setUserAgent(APP_USERAGENT);
m_oauth2->setScope(INOREADER_OAUTH_SCOPE);
connect(m_oauth2, &QOAuth2AuthorizationCodeFlow::statusChanged, [=](QAbstractOAuth::Status status) {
qDebug("Inoreader: Status changed to '%d'.", (int)status);
});
connect(oauth_reply_handler, &QOAuthHttpServerReplyHandler::tokensReceived, this, &InoreaderNetworkFactory::tokensReceived);
m_oauth2->setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QVariantMap* parameters) {
qDebug() << "Inoreader: Set modify parameters for stage" << (int)stage << "called: \n" << parameters;
#if defined(Q_OS_LINUX)
if (stage == QAbstractOAuth::Stage::RefreshingAccessToken) {
parameters->insert(QSL("client_id"), INOREADER_OAUTH_CLI_ID);
parameters->insert(QSL("client_secret"), INOREADER_OAUTH_CLI_KEY);
parameters->remove(QSL("redirect_uri"));
}
#endif
});
connect(m_oauth2, &QOAuth2AuthorizationCodeFlow::granted, [=]() {
qDebug("Inoreader: Oauth2 granted.");
emit accessGranted();
});
connect(m_oauth2, &QOAuth2AuthorizationCodeFlow::error, [=](QString err, QString error_description, QUrl uri) {
Q_UNUSED(err)
Q_UNUSED(uri)
qCritical("Inoreader: We have error: '%s'.", qPrintable(error_description));
setRefreshToken(QString());
setAccessToken(QString());
emit error(error_description);
});
connect(m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, [](const QUrl& url) {
qApp->web()->openUrlInExternalBrowser(url.toString());
}); });
} }
@ -152,7 +102,11 @@ RootItem* InoreaderNetworkFactory::feedsCategories(bool obtain_icons) {
QMap<QString, RootItem*> cats; QMap<QString, RootItem*> cats;
cats.insert(QSL(""), parent); cats.insert(QSL(""), parent);
QNetworkReply* reply = m_oauth2->get(QUrl(INOREADER_API_LIST_LABELS)); QNetworkRequest req(QUrl(INOREADER_API_LIST_LABELS));
m_oauth2->setBearerHeader(req);
QNetworkReply* reply = SilentNetworkAccessManager::instance()->get(req);
QEventLoop loop; QEventLoop loop;
connect(reply, &QNetworkReply::finished, [&]() { connect(reply, &QNetworkReply::finished, [&]() {
@ -252,7 +206,7 @@ RootItem* InoreaderNetworkFactory::feedsCategories(bool obtain_icons) {
} }
void InoreaderNetworkFactory::setAccessToken(const QString& accessToken) { void InoreaderNetworkFactory::setAccessToken(const QString& accessToken) {
m_oauth2->setToken(accessToken); //m_oauth2->setToken(accessToken);
} }
QString InoreaderNetworkFactory::refreshToken() const { QString InoreaderNetworkFactory::refreshToken() const {
@ -260,5 +214,5 @@ QString InoreaderNetworkFactory::refreshToken() const {
} }
QString InoreaderNetworkFactory::accessToken() const { QString InoreaderNetworkFactory::accessToken() const {
return m_oauth2->token(); return "a";// m_oauth2->token();
} }

View File

@ -24,7 +24,7 @@
#include <QNetworkReply> #include <QNetworkReply>
class RootItem; class RootItem;
class QOAuth2AuthorizationCodeFlow; class OAuth2Service;
class InoreaderNetworkFactory : public QObject { class InoreaderNetworkFactory : public QObject {
Q_OBJECT Q_OBJECT
@ -70,7 +70,7 @@ class InoreaderNetworkFactory : public QObject {
QString m_username; QString m_username;
QString m_refreshToken; QString m_refreshToken;
int m_batchSize; int m_batchSize;
QOAuth2AuthorizationCodeFlow* m_oauth2; OAuth2Service* m_oauth2;
}; };
#endif // INOREADERNETWORKFACTORY_H #endif // INOREADERNETWORKFACTORY_H