| /**************************************************************************** |
| ** |
| ** 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 ¶meters) |
| { |
| 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 ¶meters, |
| 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 ¶meters, |
| 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 ¶meters) |
| { |
| 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 ¶meters) |
| { |
| 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 ¶meters) |
| { |
| 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 ¶meters) |
| { |
| 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 ¶meters) |
| { |
| 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 ¶meters) |
| { |
| 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 ¶meters) |
| { |
| 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, ¶meters); |
| |
| // 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 |