blob: 1082ce9e8a9eedb58505ca19d715c1193efed535 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Network Auth module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) 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.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-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtNetwork/qtnetwork-config.h>
#ifndef QT_NO_HTTP
#include "qoauth1.h"
#include "qoauth1_p.h"
#include "qoauth1signature.h"
#include "qoauthoobreplyhandler.h"
#include "qoauthhttpserverreplyhandler.h"
#include <QtCore/qmap.h>
#include <QtCore/qlist.h>
#include <QtCore/qvariant.h>
#include <QtCore/qurlquery.h>
#include <QtCore/qdatetime.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qmessageauthenticationcode.h>
#include <QtNetwork/qnetworkreply.h>
#include <QtNetwork/qnetworkrequest.h>
#include <QtNetwork/qnetworkaccessmanager.h>
QT_BEGIN_NAMESPACE
/*!
\class QOAuth1
\inmodule QtNetworkAuth
\ingroup oauth
\brief The QOAuth1 class provides an implementation of the
\l {https://tools.ietf.org/html/rfc5849}{OAuth 1 Protocol}.
\since 5.8
QOAuth1 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.
QOAuth1 uses tokens to represent the authorization granted to the
client by the resource owner. Typically, token credentials are
issued by the server at the resource owner's request, after
authenticating the resource owner's identity (usually using a
username and password).
When making the temporary credentials request, the client
authenticates using only the client credentials. When making the
token request, the client authenticates using the client
credentials as well as the temporary credentials. Once the
client receives and stores the token credentials, it can
proceed to access protected resources on behalf of the resource
owner by making authenticated requests using the client
credentials together with the token credentials received.
*/
/*!
\enum QOAuth1::SignatureMethod
Indicates the signature method to be used to sign requests.
\value Hmac_Sha1
\l {https://tools.ietf.org/html/rfc5849#section-3.4.2}
{HMAC-SHA1} signature method.
\value Rsa_Sha1
\l {https://tools.ietf.org/html/rfc5849#section-3.4.3}
{RSA-SHA1} signature method (not supported).
\value PlainText
\l {https://tools.ietf.org/html/rfc5849#section-3.4.4}
{PLAINTEXT} signature method.
*/
using Key = QOAuth1Private::OAuth1KeyString;
const QString Key::oauthCallback = QStringLiteral("oauth_callback");
const QString Key::oauthCallbackConfirmed = QStringLiteral("oauth_callback_confirmed");
const QString Key::oauthConsumerKey = QStringLiteral("oauth_consumer_key");
const QString Key::oauthNonce = QStringLiteral("oauth_nonce");
const QString Key::oauthSignature = QStringLiteral("oauth_signature");
const QString Key::oauthSignatureMethod = QStringLiteral("oauth_signature_method");
const QString Key::oauthTimestamp = QStringLiteral("oauth_timestamp");
const QString Key::oauthToken = QStringLiteral("oauth_token");
const QString Key::oauthTokenSecret = QStringLiteral("oauth_token_secret");
const QString Key::oauthVerifier = QStringLiteral("oauth_verifier");
const QString Key::oauthVersion = QStringLiteral("oauth_version");
QOAuth1Private::QOAuth1Private(const QPair<QString, QString> &clientCredentials,
QNetworkAccessManager *networkAccessManager) :
QAbstractOAuthPrivate("qt.networkauth.oauth1",
QUrl(),
clientCredentials.first,
networkAccessManager),
clientIdentifierSharedKey(clientCredentials.second)
{
qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
qRegisterMetaType<QOAuth1::SignatureMethod>("QOAuth1::SignatureMethod");
}
void QOAuth1Private::appendCommonHeaders(QVariantMap *headers)
{
const auto currentDateTime = QDateTime::currentDateTimeUtc();
headers->insert(Key::oauthNonce, QOAuth1::nonce());
headers->insert(Key::oauthConsumerKey, clientIdentifier);
headers->insert(Key::oauthTimestamp, QString::number(currentDateTime.toSecsSinceEpoch()));
headers->insert(Key::oauthVersion, oauthVersion);
headers->insert(Key::oauthSignatureMethod, signatureMethodString().toUtf8());
}
void QOAuth1Private::appendSignature(QAbstractOAuth::Stage stage,
QVariantMap *headers,
const QUrl &url,
QNetworkAccessManager::Operation operation,
const QVariantMap parameters)
{
QByteArray signature;
{
QMultiMap<QString, QVariant> headerCopy = *headers;
QVariantMap allParameters = headerCopy.unite(parameters);
if (modifyParametersFunction)
modifyParametersFunction(stage, &allParameters);
signature = generateSignature(allParameters, url, operation);
}
headers->insert(Key::oauthSignature, signature);
}
QNetworkReply *QOAuth1Private::requestToken(QNetworkAccessManager::Operation operation,
const QUrl &url,
const QPair<QString, QString> &token,
const QVariantMap &parameters)
{
if (Q_UNLIKELY(!networkAccessManager())) {
qCWarning(loggingCategory, "QNetworkAccessManager not available");
return nullptr;
}
if (Q_UNLIKELY(url.isEmpty())) {
qCWarning(loggingCategory, "Request Url not set");
return nullptr;
}
if (Q_UNLIKELY(operation != QNetworkAccessManager::GetOperation &&
operation != QNetworkAccessManager::PostOperation)) {
qCWarning(loggingCategory, "Operation not supported");
return nullptr;
}
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
QAbstractOAuth::Stage stage = QAbstractOAuth::Stage::RequestingTemporaryCredentials;
QVariantMap headers;
QVariantMap remainingParameters;
appendCommonHeaders(&headers);
for (auto it = parameters.begin(), end = parameters.end(); it != end; ++it) {
const auto key = it.key();
const auto value = it.value();
if (key.startsWith(QStringLiteral("oauth_")))
headers.insert(key, value);
else
remainingParameters.insert(key, value);
}
if (!token.first.isEmpty()) {
headers.insert(Key::oauthToken, token.first);
stage = QAbstractOAuth::Stage::RequestingAccessToken;
}
appendSignature(stage, &headers, url, operation, remainingParameters);
request.setRawHeader("Authorization", QOAuth1::generateAuthorizationHeader(headers));
QNetworkReply *reply = nullptr;
if (operation == QNetworkAccessManager::GetOperation) {
if (parameters.size() > 0) {
QUrl url = request.url();
url.setQuery(QOAuth1Private::createQuery(remainingParameters));
request.setUrl(url);
}
reply = networkAccessManager()->get(request);
}
else if (operation == QNetworkAccessManager::PostOperation) {
QUrlQuery query = QOAuth1Private::createQuery(remainingParameters);
const QByteArray data = query.toString(QUrl::FullyEncoded).toUtf8();
request.setHeader(QNetworkRequest::ContentTypeHeader,
QStringLiteral("application/x-www-form-urlencoded"));
reply = networkAccessManager()->post(request, data);
}
connect(reply, &QNetworkReply::errorOccurred,
this, &QOAuth1Private::_q_onTokenRequestError);
QAbstractOAuthReplyHandler *handler = replyHandler ? replyHandler.data()
: defaultReplyHandler.data();
QObject::connect(reply, &QNetworkReply::finished,
[handler, reply]() { handler->networkReplyFinished(reply); });
connect(handler, &QAbstractOAuthReplyHandler::tokensReceived, this,
&QOAuth1Private::_q_tokensReceived);
return reply;
}
QString QOAuth1Private::signatureMethodString() const
{
switch (signatureMethod) { // No default: intended
case QOAuth1::SignatureMethod::PlainText:
return QStringLiteral("PLAINTEXT");
case QOAuth1::SignatureMethod::Hmac_Sha1:
return QStringLiteral("HMAC-SHA1");
case QOAuth1::SignatureMethod::Rsa_Sha1:
qFatal("RSA-SHA1 signature method not supported");
return QStringLiteral("RSA-SHA1");
}
qFatal("Invalid signature method");
return QString();
}
QByteArray QOAuth1Private::generateSignature(const QVariantMap &parameters,
const QUrl &url,
QNetworkAccessManager::Operation operation) const
{
QOAuth1Signature signature(url,
clientIdentifierSharedKey,
tokenSecret,
static_cast<QOAuth1Signature::HttpRequestMethod>(operation),
parameters);
return formatSignature(signature);
}
QByteArray QOAuth1Private::generateSignature(const QVariantMap &parameters,
const QUrl &url,
const QByteArray &verb) const
{
QOAuth1Signature signature(url,
clientIdentifierSharedKey,
tokenSecret,
QOAuth1Signature::HttpRequestMethod::Custom,
parameters);
signature.setCustomMethodString(verb);
return formatSignature(signature);
}
QByteArray QOAuth1Private::formatSignature(const QOAuth1Signature &signature) const
{
switch (signatureMethod) {
case QOAuth1::SignatureMethod::Hmac_Sha1:
return signature.hmacSha1().toBase64();
case QOAuth1::SignatureMethod::PlainText:
return signature.plainText();
default:
qFatal("QOAuth1Private::generateSignature: Signature method not supported");
return QByteArray();
}
}
QVariantMap QOAuth1Private::createOAuthBaseParams() const
{
QVariantMap oauthParams;
const auto currentDateTime = QDateTime::currentDateTimeUtc();
oauthParams.insert(Key::oauthConsumerKey, clientIdentifier);
oauthParams.insert(Key::oauthVersion, QStringLiteral("1.0"));
oauthParams.insert(Key::oauthToken, token);
oauthParams.insert(Key::oauthSignatureMethod, signatureMethodString());
oauthParams.insert(Key::oauthNonce, QOAuth1::nonce());
oauthParams.insert(Key::oauthTimestamp, QString::number(currentDateTime.toSecsSinceEpoch()));
return oauthParams;
}
void QOAuth1Private::prepareRequestImpl(QNetworkRequest *request,
const QByteArray &verb,
const QByteArray &body)
{
Q_Q(QOAuth1);
QVariantMap signingParams;
if (verb == "POST" &&
request->header(QNetworkRequest::ContentTypeHeader).toByteArray()
== "application/x-www-form-urlencoded") {
QUrlQuery query(QString::fromUtf8(body));
for (const auto &item : query.queryItems(QUrl::FullyDecoded))
signingParams.insert(item.first, item.second);
}
q->setup(request, signingParams, verb);
}
void QOAuth1Private::_q_onTokenRequestError(QNetworkReply::NetworkError error)
{
Q_Q(QOAuth1);
Q_UNUSED(error);
Q_EMIT q->requestFailed(QAbstractOAuth::Error::NetworkError);
}
void QOAuth1Private::_q_tokensReceived(const QVariantMap &tokens)
{
Q_Q(QOAuth1);
if (!tokenRequested && status == QAbstractOAuth::Status::TemporaryCredentialsReceived) {
// We didn't request a token yet, but in the "TemporaryCredentialsReceived" state _any_
// new tokens received will count as a successful authentication and we move to the
// 'Granted' state. To avoid this, 'status' will be temporarily set to 'NotAuthenticated'.
status = QAbstractOAuth::Status::NotAuthenticated;
}
if (tokenRequested) // 'Reset' tokenRequested now that we've gotten new tokens
tokenRequested = false;
QPair<QString, QString> credential(tokens.value(Key::oauthToken).toString(),
tokens.value(Key::oauthTokenSecret).toString());
switch (status) {
case QAbstractOAuth::Status::NotAuthenticated:
if (tokens.value(Key::oauthCallbackConfirmed, true).toBool()) {
q->setTokenCredentials(credential);
setStatus(QAbstractOAuth::Status::TemporaryCredentialsReceived);
} else {
Q_EMIT q->requestFailed(QAbstractOAuth::Error::OAuthCallbackNotVerified);
}
break;
case QAbstractOAuth::Status::TemporaryCredentialsReceived:
q->setTokenCredentials(credential);
setStatus(QAbstractOAuth::Status::Granted);
break;
case QAbstractOAuth::Status::Granted:
case QAbstractOAuth::Status::RefreshingToken:
break;
}
}
/*!
Constructs a QOAuth1 object with parent object \a parent.
*/
QOAuth1::QOAuth1(QObject *parent) :
QOAuth1(nullptr,
parent)
{}
/*!
Constructs a QOAuth1 object with parent object \a parent, using
\a manager to access the network.
*/
QOAuth1::QOAuth1(QNetworkAccessManager *manager, QObject *parent) :
QOAuth1(QString(),
QString(),
manager,
parent)
{}
/*!
Constructs a QOAuth1 object with parent object \a parent, using
\a manager to access the network.
Also sets \a clientIdentifier and \a clientSharedSecret to sign
the calls to the web server and identify the application.
*/
QOAuth1::QOAuth1(const QString &clientIdentifier,
const QString &clientSharedSecret,
QNetworkAccessManager *manager,
QObject *parent)
: QAbstractOAuth(*new QOAuth1Private(qMakePair(clientIdentifier, clientSharedSecret),
manager),
parent)
{}
/*!
Returns the current shared secret used to sign requests to
the web server.
\sa setClientSharedSecret(), clientCredentials()
*/
QString QOAuth1::clientSharedSecret() const
{
Q_D(const QOAuth1);
return d->clientIdentifierSharedKey;
}
/*!
Sets \a clientSharedSecret as the string used to sign the
requests to the web server.
\sa clientSharedSecret(), setClientCredentials()
*/
void QOAuth1::setClientSharedSecret(const QString &clientSharedSecret)
{
Q_D(QOAuth1);
if (d->clientIdentifierSharedKey != clientSharedSecret) {
d->clientIdentifierSharedKey = clientSharedSecret;
Q_EMIT clientSharedSecretChanged(clientSharedSecret);
}
}
/*!
Returns the pair of QString used to identify the application and
sign requests to the web server.
\sa setClientCredentials()
*/
QPair<QString, QString> QOAuth1::clientCredentials() const
{
Q_D(const QOAuth1);
return qMakePair(d->clientIdentifier, d->clientIdentifierSharedKey);
}
/*!
Sets \a clientCredentials as the pair of QString used to identify
the application and sign requests to the web server.
\sa clientCredentials()
*/
void QOAuth1::setClientCredentials(const QPair<QString, QString> &clientCredentials)
{
setClientCredentials(clientCredentials.first, clientCredentials.second);
}
/*!
Sets \a clientIdentifier and \a clientSharedSecret as the pair of
QString used to identify the application and sign requests to the
web server. \a clientIdentifier identifies the application and
\a clientSharedSecret is used to sign requests.
\sa clientCredentials()
*/
void QOAuth1::setClientCredentials(const QString &clientIdentifier,
const QString &clientSharedSecret)
{
setClientIdentifier(clientIdentifier);
setClientSharedSecret(clientSharedSecret);
}
/*!
Returns the current token secret used to sign authenticated
requests to the web server.
\sa setTokenSecret(), tokenCredentials()
*/
QString QOAuth1::tokenSecret() const
{
Q_D(const QOAuth1);
return d->tokenSecret;
}
/*!
Sets \a tokenSecret as the current token secret used to sign
authenticated calls to the web server.
\sa tokenSecret(), setTokenCredentials()
*/
void QOAuth1::setTokenSecret(const QString &tokenSecret)
{
Q_D(QOAuth1);
if (d->tokenSecret != tokenSecret) {
d->tokenSecret = tokenSecret;
Q_EMIT tokenSecretChanged(tokenSecret);
}
}
/*!
Returns the pair of QString used to identify and sign
authenticated requests to the web server.
\sa setTokenCredentials()
*/
QPair<QString, QString> QOAuth1::tokenCredentials() const
{
Q_D(const QOAuth1);
return qMakePair(d->token, d->tokenSecret);
}
/*!
Sets \a tokenCredentials as the pair of QString used to identify
and sign authenticated requests to the web server.
\sa tokenCredentials()
*/
void QOAuth1::setTokenCredentials(const QPair<QString, QString> &tokenCredentials)
{
setTokenCredentials(tokenCredentials.first, tokenCredentials.second);
}
/*!
Sets \a token and \a tokenSecret as the pair of QString used to
identify and sign authenticated requests to the web server.
Once the client receives and stores the token credentials, it can
proceed to access protected resources on behalf of the resource
owner by making authenticated requests using the client
credentials together with the token credentials received.
\sa tokenCredentials()
*/
void QOAuth1::setTokenCredentials(const QString &token, const QString &tokenSecret)
{
setToken(token);
setTokenSecret(tokenSecret);
}
/*!
Returns the url used to request temporary credentials to
start the authentication process.
\sa setTemporaryCredentialsUrl()
*/
QUrl QOAuth1::temporaryCredentialsUrl() const
{
Q_D(const QOAuth1);
return d->temporaryCredentialsUrl;
}
/*!
Sets \a url as the URL to request temporary credentials to
start the authentication process.
\sa temporaryCredentialsUrl()
*/
void QOAuth1::setTemporaryCredentialsUrl(const QUrl &url)
{
Q_D(QOAuth1);
if (d->temporaryCredentialsUrl != url) {
d->temporaryCredentialsUrl = url;
Q_EMIT temporaryCredentialsUrlChanged(url);
}
}
/*!
Returns the url used to request token credentials to continue
the authentication process.
\sa setTokenCredentialsUrl()
*/
QUrl QOAuth1::tokenCredentialsUrl() const
{
Q_D(const QOAuth1);
return d->tokenCredentialsUrl;
}
/*!
Sets \a url as the URL to request the token credentials to
continue the authentication process.
\sa tokenCredentialsUrl()
*/
void QOAuth1::setTokenCredentialsUrl(const QUrl &url)
{
Q_D(QOAuth1);
if (d->tokenCredentialsUrl != url) {
d->tokenCredentialsUrl = url;
Q_EMIT tokenCredentialsUrlChanged(url);
}
}
/*!
Returns the method used to sign the request to the web server.
\sa setSignatureMethod()
*/
QOAuth1::SignatureMethod QOAuth1::signatureMethod() const
{
Q_D(const QOAuth1);
return d->signatureMethod;
}
/*!
Sets \a value as the method used to sign requests to the web
server.
\sa signatureMethod()
*/
void QOAuth1::setSignatureMethod(QOAuth1::SignatureMethod value)
{
Q_D(QOAuth1);
if (d->signatureMethod != value) {
d->signatureMethod = value;
Q_EMIT signatureMethodChanged(value);
}
}
/*!
Sends an authenticated HEAD request and returns a new
QNetworkReply. The \a url and \a parameters are used to create
the request.
\b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.4}
{Hypertext Transfer Protocol -- HTTP/1.1: HEAD}
*/
QNetworkReply *QOAuth1::head(const QUrl &url, const QVariantMap &parameters)
{
Q_D(QOAuth1);
if (!d->networkAccessManager()) {
qCWarning(d->loggingCategory, "QNetworkAccessManager not available");
return nullptr;
}
QNetworkRequest request(url);
setup(&request, parameters, QNetworkAccessManager::HeadOperation);
return d->networkAccessManager()->head(request);
}
/*!
Sends an authenticated GET request and returns a new
QNetworkReply. The \a url and \a parameters are used to create
the request.
\b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.3}
{Hypertext Transfer Protocol -- HTTP/1.1: GET}
*/
QNetworkReply *QOAuth1::get(const QUrl &url, const QVariantMap &parameters)
{
Q_D(QOAuth1);
if (!d->networkAccessManager()) {
qCWarning(d->loggingCategory, "QNetworkAccessManager not available");
return nullptr;
}
QNetworkRequest request(url);
setup(&request, parameters, QNetworkAccessManager::GetOperation);
QNetworkReply *reply = d->networkAccessManager()->get(request);
connect(reply, &QNetworkReply::finished, [this, reply]() { emit finished(reply); });
return reply;
}
/*!
Sends an authenticated POST request and returns a new
QNetworkReply. The \a url and \a parameters are used to create
the request.
\b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.5}
{Hypertext Transfer Protocol -- HTTP/1.1: POST}
*/
QNetworkReply *QOAuth1::post(const QUrl &url, const QVariantMap &parameters)
{
Q_D(QOAuth1);
if (!d->networkAccessManager()) {
qCWarning(d->loggingCategory, "QNetworkAccessManager not available");
return nullptr;
}
QNetworkRequest request(url);
setup(&request, parameters, QNetworkAccessManager::PostOperation);
d->addContentTypeHeaders(&request);
const QByteArray data = d->convertParameters(parameters);
QNetworkReply *reply = d->networkAccessManager()->post(request, data);
connect(reply, &QNetworkReply::finished, [this, reply]() { emit finished(reply); });
return reply;
}
/*!
Sends an authenticated PUT request and returns a new
QNetworkReply. The \a url and \a parameters are used to create
the request.
\b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.6}
{Hypertext Transfer Protocol -- HTTP/1.1: PUT}
*/
QNetworkReply *QOAuth1::put(const QUrl &url, const QVariantMap &parameters)
{
Q_D(QOAuth1);
if (!d->networkAccessManager()) {
qCWarning(d->loggingCategory, "QNetworkAccessManager not available");
return nullptr;
}
QNetworkRequest request(url);
setup(&request, parameters, QNetworkAccessManager::PutOperation);
d->addContentTypeHeaders(&request);
const QByteArray data = d->convertParameters(parameters);
QNetworkReply *reply = d->networkAccessManager()->put(request, data);
connect(reply, &QNetworkReply::finished, std::bind(&QAbstractOAuth::finished, this, reply));
return reply;
}
/*!
Sends an authenticated DELETE request and returns a new
QNetworkReply. The \a url and \a parameters are used to create
the request.
\b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.7}
{Hypertext Transfer Protocol -- HTTP/1.1: DELETE}
*/
QNetworkReply *QOAuth1::deleteResource(const QUrl &url, const QVariantMap &parameters)
{
Q_D(QOAuth1);
if (!d->networkAccessManager()) {
qCWarning(d->loggingCategory, "QNetworkAccessManager not available");
return nullptr;
}
QNetworkRequest request(url);
setup(&request, parameters, QNetworkAccessManager::DeleteOperation);
QNetworkReply *reply = d->networkAccessManager()->deleteResource(request);
connect(reply, &QNetworkReply::finished, [this, reply]() { emit finished(reply); });
return reply;
}
/*!
Starts the a request for temporary credentials using the request
method \a operation. The request URL is \a url and the
\a parameters shall encoded and sent during the request.
\b {See also}: \l {https://tools.ietf.org/html/rfc5849#section-2.1}
{The OAuth 1.0 Protocol: Temporary Credentials}
*/
QNetworkReply *QOAuth1::requestTemporaryCredentials(QNetworkAccessManager::Operation operation,
const QUrl &url,
const QVariantMap &parameters)
{
Q_D(QOAuth1);
d->token.clear();
d->tokenSecret.clear();
QVariantMap allParameters(parameters);
allParameters.insert(Key::oauthCallback, callback());
return d->requestToken(operation, url, qMakePair(d->token, d->tokenSecret), allParameters);
}
/*!
Starts a request for token credentials using the request
method \a operation. The request URL is \a url and the
\a parameters shall be encoded and sent during the
request. The \a temporaryToken pair of string is used to identify
and sign the request.
\b {See also}: \l {https://tools.ietf.org/html/rfc5849#section-2.3}
{The OAuth 1.0 Protocol: Token Credentials}
*/
QNetworkReply *QOAuth1::requestTokenCredentials(QNetworkAccessManager::Operation operation,
const QUrl &url,
const QPair<QString, QString> &temporaryToken,
const QVariantMap &parameters)
{
Q_D(QOAuth1);
d->tokenRequested = true;
return d->requestToken(operation, url, temporaryToken, parameters);
}
/*!
Signs the \a request using \a signingParameters and \a operation.
\overload
*/
void QOAuth1::setup(QNetworkRequest *request,
const QVariantMap &signingParameters,
QNetworkAccessManager::Operation operation)
{
Q_D(const QOAuth1);
auto oauthParams = d->createOAuthBaseParams();
// Add signature parameter
{
QMultiMap<QString, QVariant> oauthParamsCopy(oauthParams);
const auto parameters = oauthParamsCopy.unite(signingParameters);
const auto signature = d->generateSignature(parameters, request->url(), operation);
oauthParams.insert(Key::oauthSignature, signature);
}
if (operation == QNetworkAccessManager::GetOperation) {
if (signingParameters.size()) {
QUrl url = request->url();
QUrlQuery query = QUrlQuery(url.query());
for (auto it = signingParameters.begin(), end = signingParameters.end(); it != end;
++it)
query.addQueryItem(it.key(), it.value().toString());
url.setQuery(query);
request->setUrl(url);
}
}
request->setRawHeader("Authorization", generateAuthorizationHeader(oauthParams));
if (operation == QNetworkAccessManager::PostOperation
|| operation == QNetworkAccessManager::PutOperation)
request->setHeader(QNetworkRequest::ContentTypeHeader,
QStringLiteral("application/x-www-form-urlencoded"));
}
/*!
\since 5.13
Signs the \a request using \a signingParameters and \a operationVerb.
\overload
*/
void QOAuth1::setup(QNetworkRequest *request, const QVariantMap &signingParameters, const QByteArray &operationVerb)
{
Q_D(const QOAuth1);
auto oauthParams = d->createOAuthBaseParams();
// Add signature parameter
{
QMultiMap<QString, QVariant> oauthParamsCopy(oauthParams);
const auto parameters = oauthParamsCopy.unite(signingParameters);
const auto signature = d->generateSignature(parameters, request->url(), operationVerb);
oauthParams.insert(Key::oauthSignature, signature);
}
request->setRawHeader("Authorization", generateAuthorizationHeader(oauthParams));
}
/*!
Generates a nonce.
\b {See also}: \l {https://tools.ietf.org/html/rfc5849#section-3.3}
{The OAuth 1.0 Protocol: Nonce and Timestamp}
*/
QByteArray QOAuth1::nonce()
{
return QAbstractOAuth::generateRandomString(8);
}
/*!
Generates an authorization header using \a oauthParams.
\b {See also}: \l {https://tools.ietf.org/html/rfc5849#section-3.5.1}
{The OAuth 1.0 Protocol: Authorization Header}
*/
QByteArray QOAuth1::generateAuthorizationHeader(const QVariantMap &oauthParams)
{
// TODO Add realm parameter support
bool first = true;
QString ret(QStringLiteral("OAuth "));
QVariantMap headers(oauthParams);
for (auto it = headers.begin(), end = headers.end(); it != end; ++it) {
if (first)
first = false;
else
ret += QLatin1String(",");
ret += it.key() +
QLatin1String("=\"") +
QString::fromUtf8(QUrl::toPercentEncoding(it.value().toString())) +
QLatin1Char('\"');
}
return ret.toUtf8();
}
/*!
Starts the Redirection-Based Authorization flow.
\note For an out-of-band reply handler, a verifier string is
received after the call to this function; pass that to
continueGrantWithVerifier() to continue the grant process.
\sa continueGrantWithVerifier()
\b {See also}: \l {https://tools.ietf.org/html/rfc5849#section-2}
{The OAuth 1.0 Protocol: Redirection-Based Authorization}
*/
void QOAuth1::grant()
{
Q_D(QOAuth1);
using Key = QOAuth1Private::OAuth1KeyString;
if (d->temporaryCredentialsUrl.isEmpty()) {
qCWarning(d->loggingCategory, "requestTokenUrl is empty");
return;
}
if (d->tokenCredentialsUrl.isEmpty()) {
qCWarning(d->loggingCategory, "authorizationGrantUrl is empty");
return;
}
if (!d->token.isEmpty() && status() == Status::Granted) {
qCWarning(d->loggingCategory, "Already authenticated");
return;
}
QMetaObject::Connection connection;
connection = connect(this, &QAbstractOAuth::statusChanged, [&](Status status) {
Q_D(QOAuth1);
if (status == Status::TemporaryCredentialsReceived) {
if (d->authorizationUrl.isEmpty()) {
// try upgrading token without verifier
auto reply = requestTokenCredentials(QNetworkAccessManager::PostOperation,
d->tokenCredentialsUrl,
qMakePair(d->token, d->tokenSecret));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
} else {
QVariantMap parameters;
parameters.insert(Key::oauthToken, d->token);
if (d->modifyParametersFunction)
d->modifyParametersFunction(Stage::RequestingAuthorization, &parameters);
// https://tools.ietf.org/html/rfc5849#section-2.2
resourceOwnerAuthorization(d->authorizationUrl, parameters);
}
} else if (status == Status::NotAuthenticated) {
// Inherit class called QAbstractOAuth::setStatus(Status::NotAuthenticated);
setTokenCredentials(QString(), QString());
disconnect(connection);
}
});
auto httpReplyHandler = qobject_cast<QOAuthHttpServerReplyHandler*>(replyHandler());
if (httpReplyHandler) {
connect(httpReplyHandler, &QOAuthHttpServerReplyHandler::callbackReceived, [&](
const QVariantMap &values) {
QString verifier = values.value(Key::oauthVerifier).toString();
if (verifier.isEmpty()) {
qCWarning(d->loggingCategory, "%s not found in the callback",
qPrintable(Key::oauthVerifier));
return;
}
continueGrantWithVerifier(verifier);
});
}
// requesting temporary credentials
auto reply = requestTemporaryCredentials(QNetworkAccessManager::PostOperation,
d->temporaryCredentialsUrl);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
}
/*!
Continues the Redirection-Based Authorization flow using
\a verifier. Call this function when using an Out-of-band reply
handler to supply the verifier provided by the web server.
*/
void QOAuth1::continueGrantWithVerifier(const QString &verifier)
{
// https://tools.ietf.org/html/rfc5849#section-2.3
Q_D(QOAuth1);
QVariantMap parameters;
parameters.insert(Key::oauthVerifier, verifier);
auto reply = requestTokenCredentials(QNetworkAccessManager::PostOperation,
d->tokenCredentialsUrl,
qMakePair(d->token, d->tokenSecret),
parameters);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
}
QT_END_NAMESPACE
#endif // QT_NO_HTTP