diff --git a/QtOAuth.pri b/QtOAuth.pri index f889a6306724a78a916c9c9f41dfed0ee4f378b5..7b3c0e9e81daf90f9585909b2745d05326c9acb1 100644 --- a/QtOAuth.pri +++ b/QtOAuth.pri @@ -3,4 +3,4 @@ DEPENDPATH += $$OUT_PWD/../QtOAuth win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../QtOAuth/release/ -lQtOAuth else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../QtOAuth/debug/ -lQtOAuth -else:unix: LIBS += -L$$OUT_PWD/../QtOAuth/ -lQtOAuth +else:unix: LIBS += -L$$OUT_PWD/../../QtOAuth/ -lQtOAuth diff --git a/QtOAuth.pro b/QtOAuth.pro index 33aef473b08f1ed0c02a46a797f3c3b2933e64a3..e2c552e418441909c35fc0bf8c9dcb2207ebee0b 100644 --- a/QtOAuth.pro +++ b/QtOAuth.pro @@ -1,3 +1,6 @@ TEMPLATE = subdirs -SUBDIRS += QtOAuth twittertimeline redditclient +SUBDIRS += \ + QtOAuth \ + tests \ + examples diff --git a/QtOAuth/private/qoauth1_p.h b/QtOAuth/private/qoauth1_p.h index fd95cb4f6aeaaf7d00fdca097653c3bcc2ad4157..9e75806376c57f6ae501635235741a0465c9d3f6 100644 --- a/QtOAuth/private/qoauth1_p.h +++ b/QtOAuth/private/qoauth1_p.h @@ -87,8 +87,11 @@ public: QNetworkAccessManager::Operation operation, const QVariantMap parameters); - QNetworkReply *requestToken(QNetworkAccessManager::Operation operation, const QUrl &url, + QNetworkReply *requestToken(QNetworkAccessManager::Operation operation, + const QUrl &url, + const QOAuthTokenPair &token, const QVariantMap &additionalParameters); + QNetworkReply *requestAuthorizationGrant(QNetworkAccessManager::Operation operation, const QUrl &url, const QVariantMap &additionalParameters); diff --git a/QtOAuth/qoauth1.cpp b/QtOAuth/qoauth1.cpp index 6e382c891f4fae43f4df0ac153056a9fb85c9aac..b7d87516a9c22b637ef3646d07644b703e06a7b2 100644 --- a/QtOAuth/qoauth1.cpp +++ b/QtOAuth/qoauth1.cpp @@ -92,8 +92,6 @@ void QOAuth1Private::_q_onTokenRequestFinished() else { q->setToken(qMakePair(token, tokenSecret)); q->setStatus(QAbstractOAuth::Status::TokenReceived); - - q->postAuthorizationGrant(); } } @@ -187,22 +185,14 @@ void QOAuth1Private::appendSignature(QVariantMap *headers, const QUrl &url, } QNetworkReply *QOAuth1Private::requestToken(QNetworkAccessManager::Operation operation, - const QUrl &url, const QVariantMap ¶meters) + const QUrl &url, + const QOAuthTokenPair &token, + const QVariantMap ¶meters) { Q_Q(QOAuth1); - if (q->status() != QAbstractOAuth::Status::NotAuthenticated) { - qWarning("Already authenticated"); - return nullptr; - } else if (!networkAccessManager) { - qCritical("QNetworkAccessManager not available"); - return nullptr; - } - - q->setStatus(QAbstractOAuth::Status::NotAuthenticated); - q->setToken(QOAuthTokenPair()); - - extraTokens.clear(); - Q_EMIT q->extraTokensChanged(extraTokens); + Q_ASSERT(networkAccessManager); + Q_ASSERT(operation == QNetworkAccessManager::GetOperation || + operation == QNetworkAccessManager::PostOperation); QNetworkRequest request(url); request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); @@ -210,6 +200,8 @@ QNetworkReply *QOAuth1Private::requestToken(QNetworkAccessManager::Operation ope QVariantMap headers; appendCommonHeaders(&headers); headers.insert(QStringLiteral("oauth_callback"), q->callback()); + if (!token.first.isEmpty()) + headers.insert(QStringLiteral("oauth_token"), token.first); appendSignature(&headers, url, operation, parameters); request.setRawHeader("Authorization", q->generateAuthorizationHeader(headers)); @@ -382,6 +374,49 @@ QNetworkReply *QOAuth1::post(const QNetworkRequest &req, const QVariantMap ¶ return d->networkAccessManager->post(request, data.toLatin1()); } +QNetworkReply *QOAuth1::requestToken(QNetworkAccessManager::Operation operation, + const QUrl &url, + const QVariantMap &additionalParameters) +{ + Q_D(QOAuth1); + if (Q_UNLIKELY(!d->networkAccessManager)) { + qCritical("QNetworkAccessManager not available"); + return nullptr; + } + if (Q_UNLIKELY(url.isEmpty())) { + qCritical() << "Request Url not set"; + return nullptr; + } + if (Q_UNLIKELY(operation != QNetworkAccessManager::GetOperation && + operation != QNetworkAccessManager::PostOperation)) { + qCritical("Operation not supported"); + return nullptr; + } + return d->requestToken(operation, url, d->token, additionalParameters); +} + +QNetworkReply *QOAuth1::requestToken(QNetworkAccessManager::Operation operation, + const QUrl &url, + const QOAuthTokenPair &token, + const QVariantMap &additionalParameters) +{ + Q_D(QOAuth1); + if (!d->networkAccessManager) { + qCritical("QNetworkAccessManager not available"); + return nullptr; + } + if (Q_UNLIKELY(url.isEmpty())) { + qCritical() << "Request Url not set"; + return nullptr; + } + if (Q_UNLIKELY(operation != QNetworkAccessManager::GetOperation && + operation != QNetworkAccessManager::PostOperation)) { + qCritical("Operation not supported"); + return nullptr; + } + return d->requestToken(operation, url, token, additionalParameters); +} + void QOAuth1::setup(QNetworkRequest *request, const QVariantMap &signingParameters, QNetworkAccessManager::Operation operation) { @@ -551,10 +586,13 @@ void QOAuth1::grant() if (d->status == Status::NotAuthenticated) { d->setStatus(Status::TokenReceived); - postAuthorizationGrant(); + requestToken(QNetworkAccessManager::PostOperation, authorizationGrantUrl()); + } else if (d->status == Status::TokenReceived) { + d->setStatus(Status::Authenticated); + Q_EMIT granted(); } }); - postRequestToken(extraTokens()); + requestToken(QNetworkAccessManager::PostOperation, requestTokenUrl(), QOAuthTokenPair()); } void QOAuth1::setExtraTokens(const QVariantMap &tokens) @@ -563,56 +601,6 @@ void QOAuth1::setExtraTokens(const QVariantMap &tokens) d->extraTokens = tokens; } -QNetworkReply * QOAuth1::getRequestToken(const QVariantMap ¶meters) -{ - Q_D(QOAuth1); - if (!d->networkAccessManager) { - qCritical("QNetworkAccessManager not available"); - return nullptr; - } - if (Q_UNLIKELY(requestTokenUrl().isEmpty())) { - qCritical() << "Request Url not set"; - return nullptr; - } - return d->requestToken(QNetworkAccessManager::GetOperation, requestTokenUrl(), - parameters); -} - -QNetworkReply * QOAuth1::postRequestToken(const QVariantMap ¶meters) -{ - Q_D(QOAuth1); - if (Q_UNLIKELY(d->requestTokenUrl.isEmpty())) { - qCritical("Request Url not set"); - return nullptr; - } - return d->requestToken(QNetworkAccessManager::PostOperation, d->requestTokenUrl, - parameters); -} - -QNetworkReply * QOAuth1::getAuthorizationGrant(const QVariantMap ¶meters) -{ - Q_D(QOAuth1); - if (Q_UNLIKELY(d->authorizationGrantUrl.isEmpty())) { - qCritical("Authorization grant Url not set"); - return nullptr; - } - return d->requestAuthorizationGrant(QNetworkAccessManager::GetOperation, - d->authorizationGrantUrl, - parameters); -} - -QNetworkReply * QOAuth1::postAuthorizationGrant(const QVariantMap ¶meters) -{ - Q_D(QOAuth1); - if (Q_UNLIKELY(d->authorizationGrantUrl.isEmpty())) { - qCritical("Authorization grant Url not set"); - return nullptr; - } - return d->requestAuthorizationGrant(QNetworkAccessManager::PostOperation, - d->authorizationGrantUrl, - parameters); -} - QT_END_NAMESPACE #endif // QT_NO_HTTP diff --git a/QtOAuth/qoauth1.h b/QtOAuth/qoauth1.h index 3f3250abb8c72b8c5bf04c2c5dad0d5f54648478..c8c5038385a4851409eb75b992165620e38318bd 100644 --- a/QtOAuth/qoauth1.h +++ b/QtOAuth/qoauth1.h @@ -50,7 +50,9 @@ class QOAuth1Private; class Q_NETWORK_EXPORT QOAuth1: public QAbstractOAuth { Q_OBJECT - Q_PROPERTY(SignatureMethod signatureMethod READ signatureMethod WRITE setSignatureMethod + Q_PROPERTY(SignatureMethod signatureMethod + READ signatureMethod + WRITE setSignatureMethod NOTIFY signatureMethodChanged) public: @@ -68,6 +70,15 @@ public: virtual QNetworkReply *post(const QNetworkRequest &req, const QVariantMap ¶meters = QVariantMap()) override; + QNetworkReply *requestToken(QNetworkAccessManager::Operation operation, + const QUrl &url, + const QVariantMap &additionalParameters = QVariantMap()); + + QNetworkReply *requestToken(QNetworkAccessManager::Operation operation, + const QUrl &url, + const QOAuthTokenPair &token, + const QVariantMap &additionalParameters = QVariantMap()); + virtual void grant() override; Q_SIGNALS: @@ -76,12 +87,6 @@ Q_SIGNALS: protected: void setExtraTokens(const QVariantMap &tokens); - virtual QNetworkReply *getRequestToken(const QVariantMap ¶meters = QVariantMap()); - virtual QNetworkReply *postRequestToken(const QVariantMap ¶meters = QVariantMap()); - - virtual QNetworkReply *getAuthorizationGrant(const QVariantMap ¶meters = QVariantMap()); - virtual QNetworkReply *postAuthorizationGrant(const QVariantMap ¶meters = QVariantMap()); - void setup(QNetworkRequest *request, const QVariantMap &signingParameters, QNetworkAccessManager::Operation operation); diff --git a/examples/examples.pro b/examples/examples.pro new file mode 100644 index 0000000000000000000000000000000000000000..e00590dcb50ddefe7f3d07db9463ef9d0780b68c --- /dev/null +++ b/examples/examples.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + twittertimeline \ + redditclient diff --git a/redditclient/main.cpp b/examples/redditclient/main.cpp similarity index 100% rename from redditclient/main.cpp rename to examples/redditclient/main.cpp diff --git a/redditclient/redditclient.pro b/examples/redditclient/redditclient.pro similarity index 87% rename from redditclient/redditclient.pro rename to examples/redditclient/redditclient.pro index 8ccf801174062fd1b4460579c3a7b90e39df2878..ba69b60293eaa5dfa6750809038045a535981f07 100644 --- a/redditclient/redditclient.pro +++ b/examples/redditclient/redditclient.pro @@ -13,4 +13,4 @@ HEADERS += \ FORMS += -include(../QtOAuth.pri) +include(../../QtOAuth.pri) diff --git a/redditclient/redditmodel.cpp b/examples/redditclient/redditmodel.cpp similarity index 100% rename from redditclient/redditmodel.cpp rename to examples/redditclient/redditmodel.cpp diff --git a/redditclient/redditmodel.h b/examples/redditclient/redditmodel.h similarity index 100% rename from redditclient/redditmodel.h rename to examples/redditclient/redditmodel.h diff --git a/redditclient/redditwrapper.cpp b/examples/redditclient/redditwrapper.cpp similarity index 100% rename from redditclient/redditwrapper.cpp rename to examples/redditclient/redditwrapper.cpp diff --git a/redditclient/redditwrapper.h b/examples/redditclient/redditwrapper.h similarity index 100% rename from redditclient/redditwrapper.h rename to examples/redditclient/redditwrapper.h diff --git a/redditclient/uic_wrapper.sh b/examples/redditclient/uic_wrapper.sh similarity index 100% rename from redditclient/uic_wrapper.sh rename to examples/redditclient/uic_wrapper.sh diff --git a/twittertimeline/main.cpp b/examples/twittertimeline/main.cpp similarity index 100% rename from twittertimeline/main.cpp rename to examples/twittertimeline/main.cpp diff --git a/twittertimeline/twitter.cpp b/examples/twittertimeline/twitter.cpp similarity index 100% rename from twittertimeline/twitter.cpp rename to examples/twittertimeline/twitter.cpp diff --git a/twittertimeline/twitter.h b/examples/twittertimeline/twitter.h similarity index 100% rename from twittertimeline/twitter.h rename to examples/twittertimeline/twitter.h diff --git a/twittertimeline/twitterdialog.ui b/examples/twittertimeline/twitterdialog.ui similarity index 100% rename from twittertimeline/twitterdialog.ui rename to examples/twittertimeline/twitterdialog.ui diff --git a/twittertimeline/twittertimeline.pro b/examples/twittertimeline/twittertimeline.pro similarity index 92% rename from twittertimeline/twittertimeline.pro rename to examples/twittertimeline/twittertimeline.pro index dfa76042358e10c871bea689045c11970a8f290d..3929f9262c1fafbe12e89c93e1991f1699d19b06 100644 --- a/twittertimeline/twittertimeline.pro +++ b/examples/twittertimeline/twittertimeline.pro @@ -18,5 +18,5 @@ INSTALLS += target FORMS += \ twitterdialog.ui -include(../QtOAuth.pri) +include(../../QtOAuth.pri) diff --git a/twittertimeline/twittertimelinemodel.cpp b/examples/twittertimeline/twittertimelinemodel.cpp similarity index 100% rename from twittertimeline/twittertimelinemodel.cpp rename to examples/twittertimeline/twittertimelinemodel.cpp diff --git a/twittertimeline/twittertimelinemodel.h b/examples/twittertimeline/twittertimelinemodel.h similarity index 100% rename from twittertimeline/twittertimelinemodel.h rename to examples/twittertimeline/twittertimelinemodel.h diff --git a/tests/qoauth/qoauth.pro b/tests/qoauth/qoauth.pro new file mode 100644 index 0000000000000000000000000000000000000000..3cf5b39f3093ab662f1dedc2bb492b9470f18605 --- /dev/null +++ b/tests/qoauth/qoauth.pro @@ -0,0 +1,9 @@ +CONFIG += testcase +TARGET = tst_qoauth +SOURCES += tst_qoauth.cpp + +INCLUDEPATH += / + +QT = core core-private network testlib + +include(../../QtOAuth.pri) diff --git a/tests/qoauth/tst_qoauth.cpp b/tests/qoauth/tst_qoauth.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ad64e7fa3725928818fc3a5680b7d7eb4b9a7223 --- /dev/null +++ b/tests/qoauth/tst_qoauth.cpp @@ -0,0 +1,518 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 <QtCore> +#include <QtTest> +#include <QtNetwork> + +#include <qoauth1.h> +#include <qabstractoauth.h> +#include <private/qoauth1_p.h> +#include <private/qabstractoauth_p.h> + +Q_DECLARE_METATYPE(QNetworkAccessManager::Operation) +Q_DECLARE_METATYPE(QAbstractOAuth::Error) + +typedef QSharedPointer<QNetworkReply> QNetworkReplyPtr; + +class tst_QOAuth: public QObject +{ + Q_OBJECT + + QEventLoop *loop; + enum RunSimpleRequestReturn { Timeout = 0, Success, Failure }; + int returnCode; + + using QObject::connect; + static bool connect(const QNetworkReplyPtr &ptr, const char *signal, const QObject *receiver, const char *slot, Qt::ConnectionType ct = Qt::AutoConnection) + { return connect(ptr.data(), signal, receiver, slot, ct); } + bool connect(const QNetworkReplyPtr &ptr, const char *signal, const char *slot, Qt::ConnectionType ct = Qt::AutoConnection) + { return connect(ptr.data(), signal, slot, ct); } + +public: + int waitForFinish(QNetworkReplyPtr &reply); + void test_data(); + void fillParameters(QVariantMap *parameters, const QUrlQuery &query); + + +public Q_SLOTS: + void finished(); + void gotError(); + +private Q_SLOTS: + void createSignature(); + + void getToken_data(); + void getToken(); + + void grant_data(); + void grant(); + + void authenticatedCalls_data(); + void authenticatedCalls(); +}; + +int tst_QOAuth::waitForFinish(QNetworkReplyPtr &reply) +{ + int count = 0; + + connect(reply, SIGNAL(finished()), SLOT(finished())); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(gotError())); + returnCode = Success; + loop = new QEventLoop; + QSignalSpy spy(reply.data(), SIGNAL(downloadProgress(qint64,qint64))); + while (!reply->isFinished()) { + QTimer::singleShot(5000, loop, SLOT(quit())); + if ( loop->exec() == Timeout && count == spy.count() && !reply->isFinished()) { + returnCode = Timeout; + break; + } + count = spy.count(); + } + delete loop; + loop = 0; + + return returnCode; +} + +void tst_QOAuth::test_data() +{ + QTest::addColumn<QString>("consumerKey"); + QTest::addColumn<QString>("consumerSecret"); + QTest::addColumn<QString>("requestToken"); + QTest::addColumn<QString>("requestTokenSecret"); + QTest::addColumn<QString>("accessToken"); + QTest::addColumn<QString>("accessTokenSecret"); + QTest::addColumn<QUrl>("requestTokenUrl"); + QTest::addColumn<QUrl>("accessTokenUrl"); + QTest::addColumn<QUrl>("authenticatedCallUrl"); + QTest::addColumn<QNetworkAccessManager::Operation>("requestType"); + + QTest::newRow("term.ie_get") << "key" + << "secret" + << "requestkey" + << "requestsecret" + << "accesskey" + << "accesssecret" + << QUrl("http://term.ie/oauth/example/request_token.php") + << QUrl("http://term.ie/oauth/example/access_token.php") + << QUrl("http://term.ie/oauth/example/echo_api.php") + << QNetworkAccessManager::GetOperation; + QTest::newRow("term.ie_post") << "key" + << "secret" + << "requestkey" + << "requestsecret" + << "accesskey" + << "accesssecret" + << QUrl("http://term.ie/oauth/example/request_token.php") + << QUrl("http://term.ie/oauth/example/access_token.php") + << QUrl("http://term.ie/oauth/example/echo_api.php") + << QNetworkAccessManager::PostOperation; + QTest::newRow("oauthbin.com_get") << "key" + << "secret" + << "requestkey" + << "requestsecret" + << "accesskey" + << "accesssecret" + << QUrl("http://oauthbin.com/v1/request-token") + << QUrl("http://oauthbin.com/v1/access-token") + << QUrl("http://oauthbin.com/v1/echo") + << QNetworkAccessManager::GetOperation; + QTest::newRow("oauthbin.com_post") << "key" + << "secret" + << "requestkey" + << "requestsecret" + << "accesskey" + << "accesssecret" + << QUrl("http://oauthbin.com/v1/request-token") + << QUrl("http://oauthbin.com/v1/access-token") + << QUrl("http://oauthbin.com/v1/echo") + << QNetworkAccessManager::PostOperation; +} + +void tst_QOAuth::fillParameters(QVariantMap *parameters, const QUrlQuery &query) +{ + const auto list = query.queryItems(); + for (auto it = list.begin(), end = list.end(); it != end; ++it) + parameters->insert(it->first, it->second); +} + +void tst_QOAuth::finished() +{ + loop->exit(returnCode = Success); +} + +void tst_QOAuth::gotError() +{ + loop->exit(returnCode = Failure); + disconnect(QObject::sender(), SIGNAL(finished()), this, 0); +} + +void tst_QOAuth::createSignature() +{ + // Example from https://dev.twitter.com/oauth/overview/creating-signatures + + QByteArray parameterString, signatureBase, signature; + + const QUrl url(QStringLiteral( + "https://api.twitter.com/1/statuses/update.json?include_entities=true")); + const QString data = QUrl::fromPercentEncoding("status=Hello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21"); + const auto consumerSecret = QStringLiteral("kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw"); + const auto oauthTokenSecret = QStringLiteral("LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE"); + + QNetworkAccessManager::Operation operation = QNetworkAccessManager::PostOperation; + QVariantMap oauthParams; + + oauthParams.insert(QStringLiteral("oauth_consumer_key"), + QStringLiteral("xvz1evFS4wEEPTGEFPHBog")); + oauthParams.insert(QStringLiteral("oauth_nonce"), + QStringLiteral("kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg")); + oauthParams.insert(QStringLiteral("oauth_signature_method"), + QStringLiteral("HMAC-SHA1")); + oauthParams.insert(QStringLiteral("oauth_timestamp"), QStringLiteral("1318622958")); + oauthParams.insert(QStringLiteral("oauth_token"), + QStringLiteral("370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb")); + oauthParams.insert(QStringLiteral("oauth_version"), QStringLiteral("1.0")); + + QVariantMap otherParametersFromData; + fillParameters(&otherParametersFromData, QUrlQuery(data)); + + // HTTP Method + QCOMPARE(QOAuth1Private::operationName(operation), QStringLiteral("POST")); + // Base URL + QCOMPARE(url.toString(QUrl::RemoveQuery), + QStringLiteral("https://api.twitter.com/1/statuses/update.json")); + + struct OAuthSubClass : QOAuth1 { + using QOAuth1::parameterString; + using QOAuth1::signatureBase; + using QOAuth1::sign; + }; + + // Creating parameter string + { + QVariantMap parameters = oauthParams; + parameters.unite(otherParametersFromData); + fillParameters(¶meters, QUrlQuery(url.query())); + parameterString = OAuthSubClass::parameterString(parameters); + + parameters.insertMulti(parameters.keys().first(), QString()); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression("^QOAuth: duplicated key .*$")); + OAuthSubClass::parameterString(parameters); + } + + QCOMPARE(parameterString, QByteArray("include_entities=true" + "&oauth_consumer_key=xvz1evFS4wEEPTGEFPHBog" + "&oauth_nonce=kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg" + "&oauth_signature_method=HMAC-SHA1" + "&oauth_timestamp=1318622958" + "&oauth_token=370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb" + "&oauth_version=1.0" + "&status=Hello%20Ladies%20%2B%20Gentlemen%2C%20a%20signed%20OAuth%20request%21")); + + // Collecting parameters + oauthParams.unite(otherParametersFromData); + signatureBase = OAuthSubClass::signatureBase(oauthParams, url, operation); + QCOMPARE(signatureBase, QByteArray("POST&https%3A%2F%2Fapi.twitter.com%2F1%2Fstatuses%2Fupdate.json" + "&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog" + "%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg" + "%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958" + "%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb" + "%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521")); + + // Getting a signing key + signature = OAuthSubClass::sign(oauthParams, url, operation, consumerSecret, oauthTokenSecret); + QCOMPARE(signature, QByteArray("tnnArxj06cWHq44gCs1OSKk/jLY=")); +} + +void tst_QOAuth::getToken_data() +{ + QTest::addColumn<QOAuthTokenPair>("key"); + QTest::addColumn<QOAuthTokenPair>("token"); + QTest::addColumn<QOAuthTokenPair>("expectedToken"); + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QNetworkAccessManager::Operation>("requestType"); + + // term.ie + + QTest::newRow("term.ie_request_get") << qMakePair(QStringLiteral("key"), + QStringLiteral("secret")) + << qMakePair(QStringLiteral(), + QStringLiteral()) + << qMakePair(QStringLiteral("requestkey"), + QStringLiteral("requestsecret")) + << QUrl("http://term.ie/oauth/example/request_token.php") + << QNetworkAccessManager::GetOperation; + + QTest::newRow("term.ie_request_post") << qMakePair(QStringLiteral("key"), + QStringLiteral("secret")) + << qMakePair(QStringLiteral(), + QStringLiteral()) + << qMakePair(QStringLiteral("requestkey"), + QStringLiteral("requestsecret")) + << QUrl("http://term.ie/oauth/example/request_token.php") + << QNetworkAccessManager::PostOperation; + + QTest::newRow("term.ie_access_get") << qMakePair(QStringLiteral("key"), + QStringLiteral("secret")) + << qMakePair(QStringLiteral("requestkey"), + QStringLiteral("requestsecret")) + << qMakePair(QStringLiteral("accesskey"), + QStringLiteral("accesssecret")) + << QUrl("http://term.ie/oauth/example/access_token.php") + << QNetworkAccessManager::GetOperation; + + QTest::newRow("term.ie_access_post") << qMakePair(QStringLiteral("key"), + QStringLiteral("secret")) + << qMakePair(QStringLiteral("requestkey"), + QStringLiteral("requestsecret")) + << qMakePair(QStringLiteral("accesskey"), + QStringLiteral("accesssecret")) + << QUrl("http://term.ie/oauth/example/access_token.php") + << QNetworkAccessManager::PostOperation; + + // oauthbin.com + + QTest::newRow("oauthbin.com_request_get") << qMakePair(QStringLiteral("key"), + QStringLiteral("secret")) + << qMakePair(QStringLiteral(), + QStringLiteral()) + << qMakePair(QStringLiteral("requestkey"), + QStringLiteral("requestsecret")) + << QUrl("http://oauthbin.com/v1/request-token") + << QNetworkAccessManager::GetOperation; + + QTest::newRow("oauthbin.com_request_post") << qMakePair(QStringLiteral("key"), + QStringLiteral("secret")) + << qMakePair(QStringLiteral(), + QStringLiteral()) + << qMakePair(QStringLiteral("requestkey"), + QStringLiteral("requestsecret")) + << QUrl("http://oauthbin.com/v1/request-token") + << QNetworkAccessManager::PostOperation; + + QTest::newRow("oauthbin.com_access_get") << qMakePair(QStringLiteral("key"), + QStringLiteral("secret")) + << qMakePair(QStringLiteral("requestkey"), + QStringLiteral("requestsecret")) + << qMakePair(QStringLiteral("accesskey"), + QStringLiteral("accesssecret")) + << QUrl("http://oauthbin.com/v1/access-token") + << QNetworkAccessManager::GetOperation; + + QTest::newRow("oauthbin.com_access_post") << qMakePair(QStringLiteral("key"), + QStringLiteral("secret")) + << qMakePair(QStringLiteral("requestkey"), + QStringLiteral("requestsecret")) + << qMakePair(QStringLiteral("accesskey"), + QStringLiteral("accesssecret")) + << QUrl("http://oauthbin.com/v1/access-token") + << QNetworkAccessManager::PostOperation; +} + +void tst_QOAuth::getToken() +{ + QFETCH(QOAuthTokenPair, key); + QFETCH(QOAuthTokenPair, token); + QFETCH(QOAuthTokenPair, expectedToken); + QFETCH(QUrl, url); + QFETCH(QNetworkAccessManager::Operation, requestType); + + QOAuthTokenPair tokenReceived; + QNetworkAccessManager networkAccessManager; + QNetworkReplyPtr reply; + + QOAuth1 o1(&networkAccessManager); + + o1.setClientId(key); + o1.setToken(token); + o1.setRequestTokenUrl(url); + reply.reset(o1.requestToken(requestType, url)); + QVERIFY(!reply.isNull()); + connect(&o1, &QAbstractOAuth::tokenChanged, [&tokenReceived]( + const QOAuthTokenPair &token){ + tokenReceived = token; + }); + QVERIFY(waitForFinish(reply) == Success); + QVERIFY(!tokenReceived.first.isEmpty() && !tokenReceived.second.isEmpty()); +} + +void tst_QOAuth::grant_data() +{ + test_data(); +} + +void tst_QOAuth::grant() +{ + QFETCH(QString, consumerKey); + QFETCH(QString, consumerSecret); + QFETCH(QString, requestToken); + QFETCH(QString, requestTokenSecret); + QFETCH(QString, accessToken); + QFETCH(QString, accessTokenSecret); + QFETCH(QUrl, requestTokenUrl); + QFETCH(QUrl, accessTokenUrl); + + bool tokenReceived = false; + QNetworkAccessManager networkAccessManager; + + QOAuth1 o1(&networkAccessManager); + + { + QSignalSpy spy(&o1, &QAbstractOAuth::requestTokenUrlChanged); + o1.setRequestTokenUrl(requestTokenUrl); + QCOMPARE(spy.count(), 1); + } + { + QSignalSpy spy(&o1, &QAbstractOAuth::clientIdChanged); + o1.setClientId(qMakePair(consumerKey, consumerSecret)); + QCOMPARE(spy.count(), 1); + } + { + QSignalSpy spy(&o1, &QAbstractOAuth::authorizationGrantUrlChanged); + o1.setAuthorizationGrantUrl(accessTokenUrl); + QCOMPARE(spy.count(), 1); + } + connect(&o1, &QAbstractOAuth::statusChanged, [&](QAbstractOAuth::Status status) { + if (status == QAbstractOAuth::Status::TokenReceived) { + if (!requestToken.isEmpty()) + QCOMPARE(requestToken, o1.token().first); + if (!requestTokenSecret.isEmpty()) + QCOMPARE(requestTokenSecret, o1.token().second); + tokenReceived = true; + } + else if (status == QAbstractOAuth::Status::Authenticated) { + if (!accessToken.isEmpty()) + QCOMPARE(accessToken, o1.token().first); + if (!accessTokenSecret.isEmpty()) + QCOMPARE(accessTokenSecret, o1.token().second); + tokenReceived = true; + } + }); + + QEventLoop loop; + + QTimer::singleShot(10000, &loop, &QEventLoop::quit); + connect(&o1, &QOAuth1::granted, &loop, &QEventLoop::quit); + o1.grant(); + loop.exec(); + QVERIFY(tokenReceived); + QCOMPARE(o1.status(), QAbstractOAuth::Status::Authenticated); +} + +void tst_QOAuth::authenticatedCalls_data() +{ + QTest::addColumn<QString>("consumerKey"); + QTest::addColumn<QString>("consumerSecret"); + QTest::addColumn<QString>("accessKey"); + QTest::addColumn<QString>("accessKeySecret"); + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QVariantMap>("parameters"); + QTest::addColumn<QNetworkAccessManager::Operation>("operation"); + + const QVariantMap parameters { { QStringLiteral("first"), QStringLiteral("first") }, + { QStringLiteral("second"), QStringLiteral("second") }, + { QStringLiteral("third"), QStringLiteral("third") } }; + + QTest::newRow("term.ie_get") << "key" + << "secret" + << "accesskey" + << "accesssecret" + << QUrl("http://term.ie/oauth/example/echo_api.php") + << parameters + << QNetworkAccessManager::GetOperation; + QTest::newRow("term.ie_post") << "key" + << "secret" + << "accesskey" + << "accesssecret" + << QUrl("http://term.ie/oauth/example/echo_api.php") + << parameters + << QNetworkAccessManager::PostOperation; + QTest::newRow("oauthbin.com_get") << "key" + << "secret" + << "accesskey" + << "accesssecret" + << QUrl("http://oauthbin.com/v1/echo") + << parameters + << QNetworkAccessManager::GetOperation; + QTest::newRow("oauthbin.com_post") << "key" + << "secret" + << "accesskey" + << "accesssecret" + << QUrl("http://oauthbin.com/v1/echo") + << parameters + << QNetworkAccessManager::PostOperation; +} + +void tst_QOAuth::authenticatedCalls() +{ + QFETCH(QString, consumerKey); + QFETCH(QString, consumerSecret); + QFETCH(QString, accessKey); + QFETCH(QString, accessKeySecret); + QFETCH(QUrl, url); + QFETCH(QVariantMap, parameters); + QFETCH(QNetworkAccessManager::Operation, operation); + + QNetworkAccessManager networkAccessManager; + QNetworkReplyPtr reply; + QString receivedData; + QString parametersString; + { + bool first = true; + for (auto it = parameters.begin(), end = parameters.end(); it != end; ++it) { + if (first) + first = false; + else + parametersString += QLatin1Char('&'); + parametersString += it.key() + QLatin1Char('=') + it.value().toString(); + } + } + + QOAuth1 o1(&networkAccessManager); + o1.setClientId(qMakePair(consumerKey, consumerSecret)); + o1.setToken(qMakePair(accessKey, accessKeySecret)); + QNetworkRequest request(url); + if (operation == QNetworkAccessManager::GetOperation) + reply.reset(o1.get(request, parameters)); + else if (operation == QNetworkAccessManager::PostOperation) + reply.reset(o1.post(request, parameters)); + QVERIFY(!reply.isNull()); + QVERIFY(!reply->isFinished()); + + connect(&networkAccessManager, &QNetworkAccessManager::finished, [&receivedData](QNetworkReply *reply) + { + receivedData = QString::fromUtf8(reply->readAll()); + }); + QVERIFY(waitForFinish(reply) == Success); + QCOMPARE(receivedData, parametersString); + reply.clear(); +} + +QTEST_MAIN(tst_QOAuth) +#include "tst_qoauth.moc" diff --git a/tests/tests.pro b/tests/tests.pro new file mode 100644 index 0000000000000000000000000000000000000000..1ee1b13235c47d8365bd094a1ca30ec8b280661f --- /dev/null +++ b/tests/tests.pro @@ -0,0 +1,11 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + qoauth + +for(p, SUBDIRS) { + X = $$find(p, INCLUDEPATH) + p.INCLUDEPATH += /TEST + message(Configuring test $${X}) +# message(p) +}