diff options
| author | Jesus Fernandez <jesus.fernandez@theqtcompany.com> | 2016-08-15 13:13:27 +0200 |
|---|---|---|
| committer | Timur Pocheptsov <timur.pocheptsov@theqtcompany.com> | 2016-08-19 17:48:39 +0000 |
| commit | a6dc1c01da723a93e1c174a6950eb4bab8cab3fc (patch) | |
| tree | 12052b48f5f073035869e7ccda42c8a71fd6eff9 /src/oauth/qoauth2authorizationcodeflow.cpp | |
| parent | 066622370b294f4914ad2e059c7f1acaa3820f19 (diff) | |
OAuth support
New library to support OAuth1 and OAuth2 standard in Qt.
OAuth provides a method for clients to access server resources on behalf
of a resource owner (such as a different client or an end-user). It
also provides a process for end-users to authorize third-party access to
their server resources without sharing their credentials (typically, a
username and password pair), using user-agent redirections.
[ChangeLog][OAuth] Added OAuth support
Change-Id: I3971456f93bf6ddd7fd46f555202bab2eb777c15
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Diffstat (limited to 'src/oauth/qoauth2authorizationcodeflow.cpp')
| -rw-r--r-- | src/oauth/qoauth2authorizationcodeflow.cpp | 353 |
1 files changed, 353 insertions, 0 deletions
diff --git a/src/oauth/qoauth2authorizationcodeflow.cpp b/src/oauth/qoauth2authorizationcodeflow.cpp new file mode 100644 index 0000000..a41993d --- /dev/null +++ b/src/oauth/qoauth2authorizationcodeflow.cpp @@ -0,0 +1,353 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_HTTP + +#include <qoauth2authorizationcodeflow.h> +#include <private/qoauth2authorizationcodeflow_p.h> + +#include <qmap.h> +#include <qurl.h> +#include <qvariant.h> +#include <qurlquery.h> +#include <qjsonobject.h> +#include <qjsondocument.h> +#include <qauthenticator.h> +#include <qoauthhttpserverreplyhandler.h> + +#include <functional> + +QT_BEGIN_NAMESPACE + +QOAuth2AuthorizationCodeFlowPrivate::QOAuth2AuthorizationCodeFlowPrivate( + const QUrl &authorizationUrl, const QUrl &accessTokenUrl, const QString &clientIdentifier, + QNetworkAccessManager *manager) : + QAbstractOAuth2Private(qMakePair(clientIdentifier, QString()), authorizationUrl, manager), + accessTokenUrl(accessTokenUrl) +{} + +QOAuth2AuthorizationCodeFlowPrivate::QOAuth2AuthorizationCodeFlowPrivate( + QNetworkAccessManager *manager) : QAbstractOAuth2Private(manager) +{} + +void QOAuth2AuthorizationCodeFlowPrivate::_q_handleCallback(const QVariantMap &data) +{ + Q_Q(QOAuth2AuthorizationCodeFlow); + using Key = QAbstractOAuth2Private::OAuth2KeyString; + + if (status != QAbstractOAuth::Status::NotAuthenticated) { + qWarning("QOAuth2AuthorizationCodeFlow: Unexpected call"); + return; + } + + Q_ASSERT(!state.isEmpty()); + + const QString error = data.value(Key::error).toString(); + const QString code = data.value(Key::code).toString(); + const QString receivedState = data.value(Key::state).toString(); + if (error.size()) { + const QString uri = data.value(Key::errorUri).toString(); + const QString description = data.value(Key::errorDescription).toString(); + qWarning("QOAuth2AuthorizationCodeFlow: AuthenticationError: %s(%s): %s", + qPrintable(error), qPrintable(uri), qPrintable(description)); + Q_EMIT q->error(error, description, uri); + return; + } + if (code.isEmpty()) { + qWarning("QOAuth2AuthorizationCodeFlow: AuthenticationError: Code not received"); + return; + } + if (receivedState.isEmpty()) { + qWarning("QOAuth2AuthorizationCodeFlow: State not received"); + return; + } + if (state != receivedState) { + qWarning("QOAuth2AuthorizationCodeFlow: State mismatch"); + return; + } + + setStatus(QAbstractOAuth::Status::TemporaryCredentialsReceived); + + QVariantMap copy(data); + copy.remove(Key::code); + extraTokens = copy; + q->requestAccessToken(code); +} + +void QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFinished(const QVariantMap &values) +{ + Q_Q(QOAuth2AuthorizationCodeFlow); + using Key = QAbstractOAuth2Private::OAuth2KeyString; + + if (values.contains(Key::error)) { + const QString error = values.value(Key::error).toString(); + qWarning("QOAuth2AuthorizationCodeFlow Error: %s", qPrintable(error)); + return; + } + + bool ok; + const QString accessToken = values.value(Key::accessToken).toString(); + tokenType = values.value(Key::tokenType).toString(); + int expiresIn = values.value(Key::expiresIn).toInt(&ok); + if (!ok) + expiresIn = -1; + refreshToken = values.value(Key::refreshToken).toString(); + scope = values.value(Key::scope).toString(); + if (accessToken.isEmpty()) { + qWarning("QOAuth2AuthorizationCodeFlow: Access token not received"); + return; + } + q->setToken(accessToken); + + const QDateTime currentDateTime = QDateTime::currentDateTime(); + if (expiresIn > 0 && currentDateTime.secsTo(expiresAt) != expiresIn) { + expiresAt = currentDateTime.addSecs(expiresIn); + Q_EMIT q->expirationAtChanged(expiresAt); + } + + setStatus(QAbstractOAuth::Status::Granted); +} + +void QOAuth2AuthorizationCodeFlowPrivate::_q_authenticate(QNetworkReply *reply, + QAuthenticator *authenticator) +{ + if (reply == currentReply){ + const auto url = reply->url(); + if (url == accessTokenUrl) { + authenticator->setUser(clientCredentials.first); + authenticator->setPassword(QString()); + } + } +} + +QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(QObject *parent) : + QAbstractOAuth2(*new QOAuth2AuthorizationCodeFlowPrivate, parent) +{} + +QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(QNetworkAccessManager *manager, + QObject *parent) : + QAbstractOAuth2(*new QOAuth2AuthorizationCodeFlowPrivate(manager), parent) +{} + +QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(const QString &clientIdentifier, + QNetworkAccessManager *manager, + QObject *parent) : + QAbstractOAuth2(*new QOAuth2AuthorizationCodeFlowPrivate(QUrl(), QUrl(), clientIdentifier, + manager), + parent) +{} + +QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(const QUrl &authenticateUrl, + const QUrl &accessTokenUrl, + QNetworkAccessManager *manager, + QObject *parent) : + QAbstractOAuth2(*new QOAuth2AuthorizationCodeFlowPrivate(authenticateUrl, accessTokenUrl, + QString(), manager), + parent) +{} + +QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(const QString &clientIdentifier, + const QUrl &authenticateUrl, + const QUrl &accessTokenUrl, + QNetworkAccessManager *manager, + QObject *parent) : + QAbstractOAuth2(*new QOAuth2AuthorizationCodeFlowPrivate(authenticateUrl, accessTokenUrl, + clientIdentifier, manager), + parent) +{} + +QOAuth2AuthorizationCodeFlow::~QOAuth2AuthorizationCodeFlow() +{} + +QString QOAuth2AuthorizationCodeFlow::responseType() const +{ + return QStringLiteral("code"); +} + +QUrl QOAuth2AuthorizationCodeFlow::accessTokenUrl() const +{ + Q_D(const QOAuth2AuthorizationCodeFlow); + return d->accessTokenUrl; +} + +void QOAuth2AuthorizationCodeFlow::setAccessTokenUrl(const QUrl &accessTokenUrl) +{ + Q_D(QOAuth2AuthorizationCodeFlow); + if (d->accessTokenUrl != accessTokenUrl) { + d->accessTokenUrl = accessTokenUrl; + Q_EMIT accessTokenUrlChanged(accessTokenUrl); + } +} + +void QOAuth2AuthorizationCodeFlow::grant() +{ + Q_D(QOAuth2AuthorizationCodeFlow); + if (d->authorizationUrl.isEmpty()) { + qWarning("QOAuth2AuthorizationCodeFlow::grant: No authenticate Url set"); + return; + } + if (d->accessTokenUrl.isEmpty()) { + qWarning("QOAuth2AuthorizationCodeFlow::grant: No request access token Url set"); + return; + } + + resourceOwnerAuthorization(d->authorizationUrl); +} + +void QOAuth2AuthorizationCodeFlow::refreshAccessToken() +{ + Q_D(QOAuth2AuthorizationCodeFlow); + + if (d->refreshToken.isEmpty()) { + qWarning("QOAuth2AuthorizationCodeFlow::refreshAccessToken: Cannot refresh access token. " + "Empty refresh token"); + return; + } + if (d->status == Status::RefreshingToken) { + qWarning("QOAuth2AuthorizationCodeFlow::refreshAccessToken: Cannot refresh access token. " + "Refresh Access Token in progress"); + return; + } + + using Key = QAbstractOAuth2Private::OAuth2KeyString; + + QVariantMap parameters; + QNetworkRequest request(d->accessTokenUrl); + QUrlQuery query; + parameters.insert(Key::grantType, QStringLiteral("refresh_token")); + parameters.insert(Key::refreshToken, d->refreshToken); + parameters.insert(Key::redirectUri, QUrl::toPercentEncoding(callback())); + query = QAbstractOAuthPrivate::createQuery(parameters); + request.setHeader(QNetworkRequest::ContentTypeHeader, + QStringLiteral("application/x-www-form-urlencoded")); + + const QString data = query.toString(QUrl::FullyEncoded); + d->currentReply = d->networkAccessManager()->post(request, data.toUtf8()); + d->status = Status::RefreshingToken; + + connect(d->currentReply.data(), &QNetworkReply::finished, + std::bind(&QAbstractOAuthReplyHandler::networkReplyFinished, replyHandler(), + d->currentReply.data())); + connect(d->currentReply.data(), &QNetworkReply::finished, d->currentReply.data(), + &QNetworkReply::deleteLater); + QObjectPrivate::connect(d->networkAccessManager(), + &QNetworkAccessManager::authenticationRequired, + d, &QOAuth2AuthorizationCodeFlowPrivate::_q_authenticate, + Qt::UniqueConnection); +} + +QUrl QOAuth2AuthorizationCodeFlow::buildAuthenticateUrl(const QVariantMap ¶meters) +{ + Q_D(QOAuth2AuthorizationCodeFlow); + using Key = QAbstractOAuth2Private::OAuth2KeyString; + + if (d->state.isEmpty()) + setState(QAbstractOAuth2Private::generateRandomState()); + Q_ASSERT(!d->state.isEmpty()); + const QString state = d->state; + + QVariantMap p(parameters); + QUrl url(d->authorizationUrl); + p.insert(Key::responseType, responseType()); + p.insert(Key::clientIdentifier, d->clientCredentials.first); + p.insert(Key::redirectUri, callback()); + p.insert(Key::scope, d->scope); + p.insert(Key::state, state); + if (d->modifyParametersFunction) + d->modifyParametersFunction(Stage::RequestingAuthorization, &p); + url.setQuery(d->createQuery(p)); + connect(d->replyHandler.data(), &QAbstractOAuthReplyHandler::callbackReceived, this, + &QOAuth2AuthorizationCodeFlow::authorizationCallbackReceived, Qt::UniqueConnection); + setStatus(QAbstractOAuth::Status::NotAuthenticated); + qDebug("QOAuth2AuthorizationCodeFlow::buildAuthenticateUrl: %s", qPrintable(url.toString())); + return url; +} + +void QOAuth2AuthorizationCodeFlow::requestAccessToken(const QString &code) +{ + Q_D(QOAuth2AuthorizationCodeFlow); + using Key = QAbstractOAuth2Private::OAuth2KeyString; + + QVariantMap parameters; + QNetworkRequest request(d->accessTokenUrl); + QUrlQuery query; + parameters.insert(Key::grantType, QStringLiteral("authorization_code")); + parameters.insert(Key::code, QUrl::toPercentEncoding(code)); + parameters.insert(Key::redirectUri, QUrl::toPercentEncoding(callback())); + 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, ¶meters); + query = QAbstractOAuthPrivate::createQuery(parameters); + request.setHeader(QNetworkRequest::ContentTypeHeader, + QStringLiteral("application/x-www-form-urlencoded")); + + const QString data = query.toString(QUrl::FullyEncoded); + d->currentReply = d->networkAccessManager()->post(request, data.toUtf8()); + QObject::connect(d->currentReply.data(), &QNetworkReply::finished, + std::bind(&QAbstractOAuthReplyHandler::networkReplyFinished, replyHandler(), + d->currentReply.data())); + QObjectPrivate::connect(d->replyHandler.data(), &QAbstractOAuthReplyHandler::tokensReceived, d, + &QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFinished, + Qt::UniqueConnection); + QObjectPrivate::connect(d->networkAccessManager(), + &QNetworkAccessManager::authenticationRequired, + d, &QOAuth2AuthorizationCodeFlowPrivate::_q_authenticate, + Qt::UniqueConnection); +} + +void QOAuth2AuthorizationCodeFlow::resourceOwnerAuthorization(const QUrl &url, + const QVariantMap ¶meters) +{ + Q_D(QOAuth2AuthorizationCodeFlow); + if (Q_UNLIKELY(url != d->authorizationUrl)) { + qWarning("Invalid URL: %s", qPrintable(url.toString())); + return; + } + const QUrl u = buildAuthenticateUrl(parameters); + QObjectPrivate::connect(this, &QOAuth2AuthorizationCodeFlow::authorizationCallbackReceived, d, + &QOAuth2AuthorizationCodeFlowPrivate::_q_handleCallback, + Qt::UniqueConnection); + Q_EMIT authorizeWithBrowser(u); +} + +QT_END_NAMESPACE + +#endif // QT_NO_HTTP |
