Commit 46d71818 authored by Jesus Fernandez's avatar Jesus Fernandez

OAuth2 QML plugin

Google Calendar example using QML
parent fe47d98d
......@@ -2,8 +2,10 @@ TEMPLATE = subdirs
SUBDIRS += \
QtOAuth \
imports \
tests \
examples
imports.depends = QtOAuth
tests.depends = QtOAuth
examples.depends = QtOAuth
......@@ -88,7 +88,8 @@ public:
{
static const QString accessToken;
static const QString apiKey;
static const QString clientId;
static const QString clientIdentifier;
static const QString clientSharedSecret;
static const QString code;
static const QString error;
static const QString errorDescription;
......
......@@ -42,9 +42,11 @@
#ifndef QT_NO_HTTP
#include <QtCore/qstring.h>
#include <QtCore/qobject.h>
#include <QtCore/qurl.h>
#include <QtCore/qstring.h>
#include <QtNetwork/qnetworkaccessmanager.h>
#include <functional>
......@@ -71,6 +73,10 @@ class OAUTH_EXPORT QAbstractOAuth : public QObject
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
Q_PROPERTY(QVariantMap extraTokens READ extraTokens NOTIFY extraTokensChanged)
Q_PROPERTY(QUrl authorizationUrl
READ authorizationUrl
WRITE setAuthorizationUrl
NOTIFY authorizationUrlChanged)
public:
enum class Status {
......@@ -119,10 +125,14 @@ public:
QAbstractOAuthReplyHandler *replyHandler() const;
void setReplyHandler(QAbstractOAuthReplyHandler *handler);
virtual QNetworkReply *get(const QNetworkRequest &req,
const QVariantMap &parameters = QVariantMap()) = 0;
virtual QNetworkReply *post(const QNetworkRequest &req,
const QVariantMap &parameters = QVariantMap()) = 0;
Q_INVOKABLE virtual QNetworkReply *head(const QUrl &url,
const QVariantMap &parameters = QVariantMap()) = 0;
Q_INVOKABLE virtual QNetworkReply *get(const QUrl &url,
const QVariantMap &parameters = QVariantMap()) = 0;
Q_INVOKABLE virtual QNetworkReply *post(const QUrl &url,
const QVariantMap &parameters = QVariantMap()) = 0;
Q_INVOKABLE virtual QNetworkReply *deleteResource(
const QUrl &url, const QVariantMap &parameters = QVariantMap()) = 0;
ModifyParametersFunction modifyParametersFunction() const;
void setModifyParametersFunction(const ModifyParametersFunction &modifyParametersFunction);
......@@ -140,6 +150,7 @@ Q_SIGNALS:
void requestFailed(const Error error);
void authorizationRequested(const QUrl &url);
void granted();
void finished(QNetworkReply *reply);
protected:
explicit QAbstractOAuth(QAbstractOAuthPrivate &, QObject *parent = nullptr);
......
......@@ -57,7 +57,8 @@ QT_BEGIN_NAMESPACE
typedef QAbstractOAuth2Private::OAuth2KeyString Key;
const QString Key::accessToken = QStringLiteral("access_token");
const QString Key::apiKey = QStringLiteral("api_key");
const QString Key::clientId = QStringLiteral("client_id");
const QString Key::clientIdentifier = QStringLiteral("client_id");
const QString Key::clientSharedSecret = QStringLiteral("client_secret");
const QString Key::code = QStringLiteral("code");
const QString Key::error = QStringLiteral("error");
const QString Key::errorDescription = QStringLiteral("error_description");
......@@ -107,45 +108,107 @@ QAbstractOAuth2::QAbstractOAuth2(QAbstractOAuth2Private &dd, QObject *parent)
QAbstractOAuth2::~QAbstractOAuth2()
{}
QNetworkReply *QAbstractOAuth2::get(const QNetworkRequest &req, const QVariantMap &parameters)
QUrl QAbstractOAuth2::createAuthenticatedUrl(const QUrl &url, const QVariantMap &parameters)
{
typedef QAbstractOAuth2Private::OAuth2KeyString Key;
Q_D(const QAbstractOAuth2);
if (Q_UNLIKELY(d->token.isEmpty())) {
qWarning(QT_STRINGIFY(QAbstractOAuth2::createAuthenticatedUrl) ": Empty access token");
return QUrl();
}
QUrl ret = url;
QUrlQuery query(ret.query());
query.addQueryItem(Key::accessToken, d->token);
for (auto it = parameters.begin(), end = parameters.end(); it != end ;++it)
query.addQueryItem(it.key(), it.value().toString());
ret.setQuery(query);
return ret;
}
QNetworkReply *QAbstractOAuth2::head(const QUrl &url, const QVariantMap &parameters)
{
Q_D(QAbstractOAuth2);
QUrl url = req.url();
QUrlQuery query(url.query());
for (auto it = parameters.begin(), end = parameters.end(); it != end; ++it)
query.addQueryItem(it.key(), it.value().toString());
url.setQuery(query);
QUrl u(url);
u.setQuery(query);
QNetworkRequest request(req);
request.setUrl(url);
QNetworkRequest request(u);
request.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent);
const QString bearer = d->bearerFormat.arg(d->token);
request.setRawHeader("Authorization", bearer.toUtf8());
QNetworkReply *reply = d->networkAccessManager()->head(request);
connect(reply, &QNetworkReply::finished, std::bind(&QAbstractOAuth::finished, this, reply));
return reply;
}
QNetworkReply *QAbstractOAuth2::get(const QUrl &url, const QVariantMap &parameters)
{
Q_D(QAbstractOAuth2);
QUrlQuery query(url.query());
for (auto it = parameters.begin(), end = parameters.end(); it != end; ++it)
query.addQueryItem(it.key(), it.value().toString());
QUrl u(url);
u.setQuery(query);
QNetworkRequest request(u);
request.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent);
const QString bearer = d->bearerFormat.arg(d->token);
request.setRawHeader("Authorization", bearer.toUtf8());
return d->networkAccessManager()->get(request);
QNetworkReply *reply = d->networkAccessManager()->get(request);
connect(reply, &QNetworkReply::finished, std::bind(&QAbstractOAuth::finished, this, reply));
return reply;
}
QNetworkReply *QAbstractOAuth2::post(const QNetworkRequest &req, const QVariantMap &parameters)
QNetworkReply *QAbstractOAuth2::post(const QUrl &url, const QVariantMap &parameters)
{
Q_D(QAbstractOAuth2);
QUrl url = req.url();
QUrlQuery query(url.query());
auto it = parameters.begin(), end = parameters.end();
for (; it != end; ++it)
query.addQueryItem(it.key(), it.value().toString());
url.setQuery(query);
QUrl u = url;
u.setQuery(query);
QNetworkRequest request(req);
QNetworkRequest request(u);
request.setUrl(url);
request.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent);
const QString bearer = d->bearerFormat.arg(d->token);
request.setRawHeader("Authorization", bearer.toUtf8());
return d->networkAccessManager()->post(request, QByteArray());
QNetworkReply *reply = d->networkAccessManager()->post(request, QByteArray());
connect(reply, &QNetworkReply::finished, std::bind(&QAbstractOAuth::finished, this, reply));
return reply;
}
QNetworkReply *QAbstractOAuth2::deleteResource(const QUrl &url, const QVariantMap &parameters)
{
Q_D(QAbstractOAuth2);
QUrlQuery query(url.query());
for (auto it = parameters.begin(), end = parameters.end(); it != end; ++it)
query.addQueryItem(it.key(), it.value().toString());
QUrl u(url);
u.setQuery(query);
QNetworkRequest request(u);
request.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent);
const QString bearer = d->bearerFormat.arg(d->token);
request.setRawHeader("Authorization", bearer.toUtf8());
QNetworkReply *reply = d->networkAccessManager()->deleteResource(request);
connect(reply, &QNetworkReply::finished, std::bind(&QAbstractOAuth::finished, this, reply));
return reply;
}
QString QAbstractOAuth2::scope() const
......@@ -223,14 +286,19 @@ void QAbstractOAuth2::setToken(const QString &token)
}
}
quint16 QAbstractOAuth2::port() const
uint QAbstractOAuth2::port() const
{
Q_D(const QAbstractOAuth2);
return d->port;
}
void QAbstractOAuth2::setPort(quint16 port)
void QAbstractOAuth2::setPort(uint port)
{
if (port > std::numeric_limits<quint16>::max()) {
qWarning("QAbstractOAuth2::setPort: port %d is invalid. It must be in the range 0-65535.",
port);
return;
}
Q_D(QAbstractOAuth2);
if (d->port != port) {
d->port = port;
......
......@@ -50,20 +50,32 @@ class QAbstractOAuth2Private;
class OAUTH_EXPORT QAbstractOAuth2 : public QAbstractOAuth
{
Q_OBJECT
Q_PROPERTY(QString scope READ scope WRITE setScope NOTIFY scopeChanged)
Q_PROPERTY(QString clientIdentifier
READ clientIdentifier
WRITE setClientIdentifier
NOTIFY clientIdentifierChanged)
Q_PROPERTY(QString clientIdentifierSharedKey
READ clientIdentifierSharedKey
WRITE setClientIdentifierSharedKey
NOTIFY clientIdentifierSharedKeyChanged)
Q_PROPERTY(uint port READ port WRITE setPort NOTIFY portChanged)
public:
explicit QAbstractOAuth2(QObject *parent = nullptr);
explicit QAbstractOAuth2(QNetworkAccessManager *manager, QObject *parent = nullptr);
virtual ~QAbstractOAuth2();
virtual QNetworkReply *get(const QNetworkRequest &req,
Q_INVOKABLE virtual QUrl createAuthenticatedUrl(const QUrl &url,
const QVariantMap &parameters = QVariantMap());
Q_INVOKABLE virtual QNetworkReply *head(const QUrl &url,
const QVariantMap &parameters = QVariantMap()) override;
Q_INVOKABLE virtual QNetworkReply *get(const QUrl &url,
const QVariantMap &parameters = QVariantMap()) override;
virtual QNetworkReply *post(const QNetworkRequest &req,
Q_INVOKABLE virtual QNetworkReply *post(const QUrl &url,
const QVariantMap &parameters = QVariantMap()) override;
Q_INVOKABLE virtual QNetworkReply *deleteResource(const QUrl &url,
const QVariantMap &parameters = QVariantMap()) override;
QString scope() const;
void setScope(const QString &scope);
......@@ -82,8 +94,8 @@ public:
QString token() const;
void setToken(const QString &token);
quint16 port() const;
void setPort(quint16 port);
uint port() const;
void setPort(uint port);
Q_SIGNALS:
void scopeChanged(const QString &scope);
......@@ -95,7 +107,6 @@ Q_SIGNALS:
void tokenChanged(const QString &token);
void portChanged(quint16 port);
public Q_SLOTS:
protected:
explicit QAbstractOAuth2(QAbstractOAuth2Private &, QObject *parent = nullptr);
......
......@@ -263,14 +263,16 @@ QString QOAuth1Private::signatureMethodString() const
QString QOAuth1Private::operationName(QNetworkAccessManager::Operation op)
{
switch (op) {
case QNetworkAccessManager::HeadOperation:
return QStringLiteral("HEAD");
case QNetworkAccessManager::GetOperation:
return QLatin1String("GET");
case QNetworkAccessManager::PostOperation:
return QLatin1String("POST");
return QStringLiteral("GET");
case QNetworkAccessManager::PutOperation:
return QLatin1String("PUT");
return QStringLiteral("PUT");
case QNetworkAccessManager::PostOperation:
return QStringLiteral("POST");
case QNetworkAccessManager::DeleteOperation:
return QLatin1String("DEL");
return QStringLiteral("DELETE");
default:; // Other actions are not supported
}
qFatal("Invalid signature method");
......@@ -439,25 +441,38 @@ void QOAuth1::setSignatureMethod(QOAuth1::SignatureMethod value)
}
}
QNetworkReply *QOAuth1::get(const QNetworkRequest &req, const QVariantMap &parameters)
QNetworkReply *QOAuth1::head(const QUrl &url, const QVariantMap &parameters)
{
Q_D(QOAuth1);
QNetworkRequest request(url);
QVariantMap allParameters = extraTokens();
allParameters.unite(parameters);
setup(&request, parameters, QNetworkAccessManager::HeadOperation);
return d->networkAccessManager()->head(request);
}
QNetworkReply *QOAuth1::get(const QUrl &url, const QVariantMap &parameters)
{
Q_D(QOAuth1);
QNetworkRequest request(req);
QNetworkRequest request(url);
QVariantMap allParameters = extraTokens();
allParameters.unite(parameters);
setup(&request, parameters, QNetworkAccessManager::GetOperation);
return d->networkAccessManager()->get(request);
QNetworkReply *reply = d->networkAccessManager()->get(request);
connect(reply, &QNetworkReply::finished, std::bind(&QAbstractOAuth::finished, this, reply));
return reply;
}
QNetworkReply *QOAuth1::post(const QNetworkRequest &req, const QVariantMap &parameters)
QNetworkReply *QOAuth1::post(const QUrl &url, const QVariantMap &parameters)
{
Q_D(QOAuth1);
if (!d->networkAccessManager()) {
qCritical("QNetworkAccessManager not available");
return nullptr;
}
QNetworkRequest request(req);
QNetworkRequest request(url);
QVariantMap allParameters = extraTokens();
allParameters.unite(parameters);
......@@ -467,7 +482,22 @@ QNetworkReply *QOAuth1::post(const QNetworkRequest &req, const QVariantMap &para
for (auto it = parameters.begin(), end = parameters.end(); it != end; ++it)
query.addQueryItem(it.key(), it.value().toString());
QString data = query.toString(QUrl::FullyEncoded);
return d->networkAccessManager()->post(request, data.toUtf8());
QNetworkReply *reply = d->networkAccessManager()->post(request, data.toUtf8());
connect(reply, &QNetworkReply::finished, std::bind(&QAbstractOAuth::finished, this, reply));
return reply;
}
QNetworkReply *QOAuth1::deleteResource(const QUrl &url, const QVariantMap &parameters)
{
Q_D(QOAuth1);
QNetworkRequest request(url);
QVariantMap allParameters = extraTokens();
allParameters.unite(parameters);
setup(&request, parameters, QNetworkAccessManager::DeleteOperation);
QNetworkReply *reply = d->networkAccessManager()->deleteResource(request);
connect(reply, &QNetworkReply::finished, std::bind(&QAbstractOAuth::finished, this, reply));
return reply;
}
QNetworkReply *QOAuth1::requestTemporaryCredentials(QNetworkAccessManager::Operation operation,
......
......@@ -102,11 +102,14 @@ public:
void setSignatureMethod(SignatureMethod value);
public:
virtual QNetworkReply *get(const QNetworkRequest &req,
virtual QNetworkReply *head(const QUrl &url, const QVariantMap &parameters = QVariantMap());
virtual QNetworkReply *get(const QUrl &url,
const QVariantMap &parameters = QVariantMap()) override;
virtual QNetworkReply *post(const QNetworkRequest &req,
virtual QNetworkReply *post(const QUrl &url,
const QVariantMap &parameters = QVariantMap()) override;
virtual QNetworkReply *deleteResource(const QUrl &url,
const QVariantMap &parameters = QVariantMap()) override;
public Q_SLOTS:
virtual void grant() override;
......
......@@ -259,7 +259,7 @@ QUrl QOAuth2AuthorizationCodeFlow::buildAuthenticateUrl(const QVariantMap &param
QVariantMap p(parameters);
QUrl url(d->authorizationUrl);
p.insert(Key::responseType, responseType());
p.insert(Key::clientId, d->clientCredentials.first);
p.insert(Key::clientIdentifier, d->clientCredentials.first);
p.insert(Key::redirectUri, callback());
p.insert(Key::scope, d->scope);
p.insert(Key::state, state);
......@@ -286,7 +286,9 @@ void QOAuth2AuthorizationCodeFlow::requestAccessToken(const QString &code)
parameters.insert(Key::grantType, grantType());
parameters.insert(Key::code, QUrl::toPercentEncoding(code));
parameters.insert(Key::redirectUri, QUrl::toPercentEncoding(callback()));
parameters.insert(Key::clientId, QUrl::toPercentEncoding(d->clientCredentials.first));
parameters.insert(Key::clientIdentifier, QUrl::toPercentEncoding(d->clientCredentials.first));
if (!d->clientCredentials.second.isEmpty())
parameters.insert(Key::clientSharedSecret, d->clientCredentials.second);
if (d->modifyParametersFunction)
d->modifyParametersFunction(Stage::RequestingAccessToken, &parameters);
query = QAbstractOAuthPrivate::createQuery(parameters);
......
......@@ -54,6 +54,10 @@ class QOAuth2AuthorizationCodeFlowPrivate;
class OAUTH_EXPORT QOAuth2AuthorizationCodeFlow : public QAbstractOAuth2
{
Q_OBJECT
Q_PROPERTY(QUrl accessTokenUrl
READ accessTokenUrl
WRITE setAccessTokenUrl
NOTIFY accessTokenUrlChanged)
public:
explicit QOAuth2AuthorizationCodeFlow(QObject *parent = nullptr);
......
......@@ -59,6 +59,10 @@ QOAuth2ImplicitGrantFlowPrivate::QOAuth2ImplicitGrantFlowPrivate(QNetworkAccessM
QOAuth2ImplicitGrantFlowPrivate::~QOAuth2ImplicitGrantFlowPrivate()
{}
QOAuth2ImplicitGrantFlow::QOAuth2ImplicitGrantFlow(QObject *parent)
: QAbstractOAuth2(*new QOAuth2ImplicitGrantFlowPrivate(nullptr), parent)
{}
QOAuth2ImplicitGrantFlow::QOAuth2ImplicitGrantFlow(QNetworkAccessManager *manager,
QObject *parent)
: QAbstractOAuth2(*new QOAuth2ImplicitGrantFlowPrivate(manager), parent)
......@@ -102,7 +106,7 @@ QUrl QOAuth2ImplicitGrantFlow::authorizationRequestUrl(const QString &state) con
QVariantMap parameters;
QUrl url(d->authorizationUrl);
parameters.insert(Key::responseType, responseType());
parameters.insert(Key::clientId, d->clientCredentials.first);
parameters.insert(Key::clientIdentifier, d->clientCredentials.first);
parameters.insert(Key::redirectUri, callback());
parameters.insert(Key::scope, d->scope);
parameters.insert(Key::state, state);
......
......@@ -52,6 +52,7 @@ class OAUTH_EXPORT QOAuth2ImplicitGrantFlow : public QAbstractOAuth2
Q_OBJECT
public:
explicit QOAuth2ImplicitGrantFlow(QObject *parent = nullptr);
explicit QOAuth2ImplicitGrantFlow(QNetworkAccessManager *manager,
QObject *parent = nullptr);
QOAuth2ImplicitGrantFlow(const QString &clientIdentifier,
......
......@@ -3,4 +3,5 @@ TEMPLATE = subdirs
SUBDIRS += \
twittertimeline \
redditclient \
qtgcal
qtgcal \
googlecalendar
unix:!android {
isEmpty(target.path) {
qnx {
target.path = /tmp/$${TARGET}/bin
} else {
target.path = /opt/$${TARGET}/bin
}
export(target.path)
}
INSTALLS += target
}
export(INSTALLS)
QT += qml quick webengine
CONFIG += c++11
SOURCES += main.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH = $$OUT_PWD/../../qml
include(../../QtOAuth.pri)
# Default rules for deployment.
include(deployment.pri)
#include <QtCore>
#include <QtGui>
#include <QtQml>
#include <QtWebEngine>
const QString webString = QStringLiteral("web");
const QString clientIdString = QStringLiteral("client_id");
const QString authUriString = QStringLiteral("auth_uri");
const QString tokenUriString = QStringLiteral("token_uri");
const QString clientSecretString = QStringLiteral("client_secret");
const QString redirectUriString = QStringLiteral("redirect_uris");
QMap<QString, QString> readClientJson(const QString &path)
{
QMap<QString, QString> ret;
QFile file(path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qCritical() << "Failed to load file";
return ret;
}
auto document = QJsonDocument::fromJson(file.readAll());
Q_ASSERT(document.isObject());
auto webObject = document.object()[webString].toObject();
const auto saveValue = [&](const QString &key) {
ret.insert(key, webObject[key].toString());
};
saveValue(clientIdString);
saveValue(authUriString);
saveValue(authUriString);
saveValue(tokenUriString);
saveValue(clientSecretString);
ret.insert(redirectUriString, webObject[redirectUriString].toArray().first().toString());
return ret;
}
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QMap<QString, QString> clientInfo;
QCommandLineParser parser;
parser.addPositionalArgument("client_json", "Specifies the file downloaded from Google APIs "
"Manager");
parser.process(app);
if (parser.positionalArguments().size())
clientInfo = readClientJson(parser.positionalArguments().first());
QtWebEngine::initialize();
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("client_id", clientInfo.value(clientIdString));
engine.rootContext()->setContextProperty("client_secret", clientInfo.value(clientSecretString));
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
return app.exec();
}
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.0
import Qt.labs.oauth 1.0
import QtWebEngine 1.1
ApplicationWindow {
visible: true
width: 640
height: 480
property bool cacheCredentials: false
property url calendarListUrl: "https://www.googleapis.com/calendar/v3/users/me/calendarList"
property var calendars: []
title: qsTr("Google Calendar Example")
function updateCalendar(calendar) {
var id = calendar.id;
var current = (new Date()).toISOString();
var client = new XMLHttpRequest;
var calendarUrl = "https://www.googleapis.com/calendar/v3/calendars/" + id + "/events";
var parameters = {
"timeMin" : (new Date()).toISOString(),
"maxResults" : 50
}
var url = oauth2.createAuthenticatedUrl(calendarUrl, parameters);
client.onreadystatechange = function() {
if (client.readyState == 4 && client.status == 200) {
var text = client.responseText;
var obj = JSON.parse(text);
for (var i in obj.items) {
function copyAll(source, dest) {
for (var key in source)
dest[key] = source[key];
}
var object = {};
copyAll(calendar, object);
copyAll(obj.items[i], object);
calendarEventsModel.append(object);
}
}
}
client.open("GET", url, true);
client.send();
}
function update() {
calendarEventsModel.clear();
for (var index in calendars)
updateCalendar(calendars[index]);
}
ListModel { id: calendarEventsModel }
ListView {
id: calendarEventsTableView
anchors.fill: parent
model: calendarEventsModel
delegate: Rectangle {
id: rectangle
width: calendarEventsTableView.width
height: 50
color: backgroundColor
RowLayout {
Rectangle {
id: datesRectangle
width: 100
height: rectangle.height
color: "transparent"
Text {
property date startDateTime: {
var date;
if (start.date)
date = new Date(Date.parse(start.date));
else
date = new Date(Date.parse(start.dateTime));
return date;
}
property var endDateTime: {
var date;
if (end.date)
date = new Date(Date.parse(end.date));
else
date = new Date(Date.parse(end.dateTime));
return date;
}
text: startDateTime.toLocaleString(Qt.locale(), Locale.NarrowFormat) +
'\n-\n' + endDateTime.toLocaleString(Qt.locale(), Locale.NarrowFormat)
horizontalAlignment: Text.AlignHCenter
anchors.centerIn: parent
color: foregroundColor
}
}
Rectangle {
width: calendarEventsTableView.width - datesRectangle.width
height: rectangle.height
color: "transparent"
Text {
property string preffix: location ? ' (' + location + ')' : ''
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: { top: 5; left: 5; right: 5; bottom: 5 }
text: summary + preffix
color: foregroundColor
}
}
}