| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 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 <QHostInfo> |
| |
| #include <QtNetworkAuth/qoauth1.h> |
| #include <QtNetworkAuth/qoauth1signature.h> |
| |
| #include <private/qoauth1_p.h> |
| |
| #include "webserver.h" |
| |
| Q_DECLARE_METATYPE(QNetworkAccessManager::Operation) |
| Q_DECLARE_METATYPE(QAbstractOAuth::Error) |
| |
| // TODO: Test PUT and DELETE operations. |
| // TODO: Write tests to test errors. |
| // TODO: Remove common event loop |
| |
| typedef QSharedPointer<QNetworkReply> QNetworkReplyPtr; |
| |
| class tst_OAuth1 : public QObject |
| { |
| Q_OBJECT |
| |
| using StringPair = QPair<QString, QString>; |
| |
| QEventLoop *loop = nullptr; |
| 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 fillParameters(QVariantMap *parameters, const QUrlQuery &query); |
| |
| template<class Type> |
| struct PropertyTester |
| { |
| typedef Type InnerType; |
| typedef void(QOAuth1::*ConstSignalType)(const Type &); |
| typedef void(QOAuth1::*SignalType)(Type); |
| typedef QVector<std::function<void(Type *, QOAuth1 *object)>> SetterFunctions; |
| |
| private: |
| // Each entry in setters should set its first parameter to an expected value |
| // and act on its second, a QOAuth1 object, to trigger signal; this |
| // function shall check that signal is passed the value the setter previously |
| // told us to expect. |
| template<class SignalType> |
| static void runImpl(SignalType signal, const SetterFunctions &setters) |
| { |
| QOAuth1 obj; |
| Type expectedValue; |
| QSignalSpy spy(&obj, signal); |
| connect(&obj, signal, [&](const Type &value) { |
| QCOMPARE(expectedValue, value); |
| }); |
| for (const auto &setter : setters) { |
| const auto previous = expectedValue; |
| setter(&expectedValue, &obj); |
| QVERIFY(previous != expectedValue); // To check if the value was modified |
| } |
| QCOMPARE(spy.count(), setters.size()); // The signal should be emitted |
| } |
| |
| public: |
| |
| static void run(ConstSignalType signal, const SetterFunctions &setters) |
| { |
| runImpl(signal, setters); |
| } |
| |
| static void run(SignalType signal, const SetterFunctions &setters) |
| { |
| runImpl(signal, setters); |
| } |
| }; |
| |
| QVariantMap parseAuthorizationString(const QString &string) |
| { |
| const QString prefix = QStringLiteral("OAuth "); |
| QVariantMap ret; |
| Q_ASSERT(string.startsWith(prefix)); |
| QRegularExpression rx("(?<key>.[^=]*)=\"(?<value>.[^\"]*)\",?"); |
| auto globalMatch = rx.globalMatch(string, prefix.size()); |
| while (globalMatch.hasNext()) { |
| const auto match = globalMatch.next(); |
| auto key = match.captured("key"); |
| QString value = match.captured("value"); |
| value = QString::fromUtf8(QByteArray::fromPercentEncoding(value.toUtf8())); |
| ret.insert(key, value); |
| } |
| return ret; |
| } |
| |
| public Q_SLOTS: |
| void finished(); |
| void gotError(); |
| |
| private Q_SLOTS: |
| void clientIdentifierSignal(); |
| void clientSharedSecretSignal(); |
| void tokenSignal(); |
| void tokenSecretSignal(); |
| void temporaryCredentialsUrlSignal(); |
| void temporaryTokenCredentialsUrlSignal(); |
| void tokenCredentialsUrlSignal(); |
| void signatureMethodSignal(); |
| |
| void getToken_data(); |
| void getToken(); |
| |
| void prepareRequestSignature_data(); |
| void prepareRequestSignature(); |
| |
| void grant_data(); |
| void grant(); |
| |
| void authenticatedCalls_data(); |
| void authenticatedCalls(); |
| |
| void prepareRequestCalls_data(); |
| void prepareRequestCalls(); |
| |
| void secondTemporaryToken(); |
| }; |
| |
| const auto oauthVersion = QStringLiteral("oauth_version"); |
| const auto oauthConsumerKey = QStringLiteral("oauth_consumer_key"); |
| const auto oauthNonce = QStringLiteral("oauth_nonce"); |
| const auto oauthSignatureMethod = QStringLiteral("oauth_signature_method"); |
| const auto oauthTimestamp = QStringLiteral("oauth_timestamp"); |
| const auto oauthToken = QStringLiteral("oauth_token"); |
| const auto oauthSignature = QStringLiteral("oauth_signature"); |
| |
| bool hostReachable(const QLatin1String &host) |
| { |
| // check host exists |
| QHostInfo hostInfo = QHostInfo::fromName(host); |
| if (hostInfo.error() != QHostInfo::NoError) |
| return false; |
| |
| // try to connect to host |
| QTcpSocket socket; |
| socket.connectToHost(host, 80); |
| if (!socket.waitForConnected(1000)) |
| return false; |
| |
| return true; |
| } |
| |
| int tst_OAuth1::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 = nullptr; |
| |
| return returnCode; |
| } |
| |
| void tst_OAuth1::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_OAuth1::finished() |
| { |
| if (loop) |
| loop->exit(returnCode = Success); |
| } |
| |
| void tst_OAuth1::gotError() |
| { |
| if (loop) |
| loop->exit(returnCode = Failure); |
| disconnect(QObject::sender(), SIGNAL(finished()), this, nullptr); |
| } |
| |
| void tst_OAuth1::clientIdentifierSignal() |
| { |
| using PropertyTester = PropertyTester<QString>; |
| PropertyTester::SetterFunctions setters { |
| [](QString *expectedValue, QOAuth1 *object) { |
| *expectedValue = "setClientIdentifier"; |
| object->setClientIdentifier(*expectedValue); |
| }, |
| [](QString *expectedValue, QOAuth1 *object) { |
| *expectedValue = "setClientCredentials"; |
| object->setClientCredentials(qMakePair(*expectedValue, QString())); |
| } |
| }; |
| PropertyTester::run(&QOAuth1::clientIdentifierChanged, setters); |
| } |
| |
| void tst_OAuth1::clientSharedSecretSignal() |
| { |
| using PropertyTester = PropertyTester<QString>; |
| PropertyTester::SetterFunctions setters { |
| [](QString *expectedValue, QOAuth1 *object) { |
| *expectedValue = "setClientSharedSecret"; |
| object->setClientSharedSecret(*expectedValue); |
| }, |
| [](QString *expectedValue, QOAuth1 *object) { |
| *expectedValue = "setClientCredentials"; |
| object->setClientCredentials(qMakePair(QString(), *expectedValue)); |
| } |
| }; |
| PropertyTester::run(&QOAuth1::clientSharedSecretChanged, setters); |
| } |
| |
| void tst_OAuth1::tokenSignal() |
| { |
| using PropertyTester = PropertyTester<QString>; |
| PropertyTester::SetterFunctions setters { |
| [](QString *expectedValue, QOAuth1 *object) { |
| *expectedValue = "setToken"; |
| object->setToken(*expectedValue); |
| }, |
| [](QString *expectedValue, QOAuth1 *object) { |
| *expectedValue = "setTokenCredentials"; |
| object->setTokenCredentials(qMakePair(*expectedValue, QString())); |
| } |
| }; |
| PropertyTester::run(&QOAuth1::tokenChanged, setters); |
| } |
| |
| void tst_OAuth1::tokenSecretSignal() |
| { |
| using PropertyTester = PropertyTester<QString>; |
| PropertyTester::SetterFunctions setters { |
| [](QString *expectedValue, QOAuth1 *object) { |
| *expectedValue = "setTokenSecret"; |
| object->setTokenSecret(*expectedValue); |
| }, |
| [](QString *expectedValue, QOAuth1 *object) { |
| *expectedValue = "setTokenCredentials"; |
| object->setTokenCredentials(qMakePair(QString(), *expectedValue)); |
| } |
| }; |
| PropertyTester::run(&QOAuth1::tokenSecretChanged, setters); |
| } |
| |
| void tst_OAuth1::temporaryCredentialsUrlSignal() |
| { |
| using PropertyTester = PropertyTester<QUrl>; |
| PropertyTester::SetterFunctions setters { |
| [](QUrl *expectedValue, QOAuth1 *object) { |
| *expectedValue = QUrl("http://example.net/"); |
| object->setTemporaryCredentialsUrl(*expectedValue); |
| } |
| }; |
| PropertyTester::run(&QOAuth1::temporaryCredentialsUrlChanged, setters); |
| } |
| |
| void tst_OAuth1::temporaryTokenCredentialsUrlSignal() |
| { |
| using PropertyTester = PropertyTester<QUrl>; |
| PropertyTester::SetterFunctions setters { |
| [](QUrl *expectedValue, QOAuth1 *object) { |
| *expectedValue = QUrl("http://example.net/"); |
| object->setTemporaryCredentialsUrl(*expectedValue); |
| } |
| }; |
| PropertyTester::run(&QOAuth1::temporaryCredentialsUrlChanged, setters); |
| } |
| |
| void tst_OAuth1::tokenCredentialsUrlSignal() |
| { |
| using PropertyTester = PropertyTester<QUrl>; |
| PropertyTester::SetterFunctions setters { |
| [](QUrl *expectedValue, QOAuth1 *object) { |
| *expectedValue = QUrl("http://example.net/"); |
| object->setTokenCredentialsUrl(*expectedValue); |
| } |
| }; |
| PropertyTester::run(&QOAuth1::tokenCredentialsUrlChanged, setters); |
| } |
| |
| void tst_OAuth1::signatureMethodSignal() |
| { |
| using PropertyTester = PropertyTester<QOAuth1::SignatureMethod>; |
| PropertyTester::SetterFunctions setters { |
| [](PropertyTester::InnerType *expectedValue, QOAuth1 *object) { |
| QVERIFY(object->signatureMethod() != QOAuth1::SignatureMethod::PlainText); |
| *expectedValue = QOAuth1::SignatureMethod::PlainText; |
| object->setSignatureMethod(*expectedValue); |
| } |
| }; |
| PropertyTester::run(&QOAuth1::signatureMethodChanged, setters); |
| } |
| |
| void tst_OAuth1::getToken_data() |
| { |
| QTest::addColumn<StringPair>("clientCredentials"); |
| QTest::addColumn<StringPair>("token"); |
| QTest::addColumn<StringPair>("expectedToken"); |
| QTest::addColumn<QOAuth1::SignatureMethod>("signatureMethod"); |
| QTest::addColumn<QNetworkAccessManager::Operation>("requestType"); |
| |
| const StringPair emptyCredentials; |
| QTest::newRow("temporary_get_plainText") |
| << qMakePair(QStringLiteral("key"), QStringLiteral("secret")) |
| << emptyCredentials |
| << qMakePair(QStringLiteral("requestkey"), QStringLiteral("requestsecret")) |
| << QOAuth1::SignatureMethod::PlainText |
| << QNetworkAccessManager::GetOperation; |
| |
| QTest::newRow("temporary_get_hmacSha1") |
| << qMakePair(QStringLiteral("key"), QStringLiteral("secret")) |
| << emptyCredentials |
| << qMakePair(QStringLiteral("requestkey"), QStringLiteral("requestsecret")) |
| << QOAuth1::SignatureMethod::Hmac_Sha1 |
| << QNetworkAccessManager::GetOperation; |
| |
| QTest::newRow("temporary_post_plainText") |
| << qMakePair(QStringLiteral("key"), QStringLiteral("secret")) |
| << emptyCredentials |
| << qMakePair(QStringLiteral("requestkey"), QStringLiteral("requestsecret")) |
| << QOAuth1::SignatureMethod::PlainText |
| << QNetworkAccessManager::PostOperation; |
| |
| QTest::newRow("temporary_post_hmacSha1") |
| << qMakePair(QStringLiteral("key"), QStringLiteral("secret")) |
| << emptyCredentials |
| << qMakePair(QStringLiteral("requestkey"), QStringLiteral("requestsecret")) |
| << QOAuth1::SignatureMethod::Hmac_Sha1 |
| << QNetworkAccessManager::PostOperation; |
| |
| QTest::newRow("token_get_plainText") |
| << qMakePair(QStringLiteral("key"), QStringLiteral("secret")) |
| << qMakePair(QStringLiteral("requestkey"), QStringLiteral("requestsecret")) |
| << qMakePair(QStringLiteral("accesskey"), QStringLiteral("accesssecret")) |
| << QOAuth1::SignatureMethod::PlainText |
| << QNetworkAccessManager::GetOperation; |
| |
| QTest::newRow("token_get_hmacSha1") |
| << qMakePair(QStringLiteral("key"), QStringLiteral("secret")) |
| << qMakePair(QStringLiteral("requestkey"), QStringLiteral("requestsecret")) |
| << qMakePair(QStringLiteral("accesskey"), QStringLiteral("accesssecret")) |
| << QOAuth1::SignatureMethod::Hmac_Sha1 |
| << QNetworkAccessManager::GetOperation; |
| |
| QTest::newRow("token_post_plainText") |
| << qMakePair(QStringLiteral("key"), QStringLiteral("secret")) |
| << qMakePair(QStringLiteral("requestkey"), QStringLiteral("requestsecret")) |
| << qMakePair(QStringLiteral("accesskey"), QStringLiteral("accesssecret")) |
| << QOAuth1::SignatureMethod::PlainText |
| << QNetworkAccessManager::PostOperation; |
| |
| QTest::newRow("token_post_hmacSha1") |
| << qMakePair(QStringLiteral("key"), QStringLiteral("secret")) |
| << qMakePair(QStringLiteral("requestkey"), QStringLiteral("requestsecret")) |
| << qMakePair(QStringLiteral("accesskey"), QStringLiteral("accesssecret")) |
| << QOAuth1::SignatureMethod::Hmac_Sha1 |
| << QNetworkAccessManager::PostOperation; |
| } |
| |
| void tst_OAuth1::getToken() |
| { |
| QFETCH(StringPair, clientCredentials); |
| QFETCH(StringPair, token); |
| QFETCH(StringPair, expectedToken); |
| QFETCH(QOAuth1::SignatureMethod, signatureMethod); |
| QFETCH(QNetworkAccessManager::Operation, requestType); |
| |
| StringPair tokenReceived; |
| QNetworkAccessManager networkAccessManager; |
| QNetworkReplyPtr reply; |
| QVariantMap oauthHeaders; |
| |
| WebServer webServer([&](const WebServer::HttpRequest &request, QTcpSocket *socket) { |
| oauthHeaders = parseAuthorizationString(request.headers["Authorization"]); |
| const QString format = "oauth_token=%1&oauth_token_secret=%2"; |
| const QByteArray text = format.arg(expectedToken.first, expectedToken.second).toUtf8(); |
| const QByteArray replyMessage { |
| "HTTP/1.0 200 OK\r\n" |
| "Content-Type: application/x-www-form-urlencoded; charset=\"utf-8\"\r\n" |
| "Content-Length: " + QByteArray::number(text.size()) + "\r\n\r\n" |
| + text |
| }; |
| socket->write(replyMessage); |
| }); |
| |
| struct OAuth1 : QOAuth1 |
| { |
| OAuth1(QNetworkAccessManager *manager) : QOAuth1(manager) {} |
| using QOAuth1::requestTokenCredentials; |
| } o1(&networkAccessManager); |
| const auto url = webServer.url(QStringLiteral("token")); |
| |
| o1.setSignatureMethod(signatureMethod); |
| o1.setClientCredentials(clientCredentials.first, clientCredentials.second); |
| o1.setTokenCredentials(token); |
| o1.setTemporaryCredentialsUrl(url); |
| QVariantMap parameters {{ "c2&a3", "c2=a3" }}; |
| reply.reset(o1.requestTokenCredentials(requestType, url, token, parameters)); |
| QVERIFY(!reply.isNull()); |
| connect(&o1, &QOAuth1::tokenChanged, [&tokenReceived](const QString &token){ |
| tokenReceived.first = token; |
| }); |
| connect(&o1, &QOAuth1::tokenSecretChanged, [&tokenReceived](const QString &tokenSecret) { |
| tokenReceived.second = tokenSecret; |
| }); |
| QVERIFY(waitForFinish(reply) == Success); |
| QCOMPARE(tokenReceived, expectedToken); |
| QCOMPARE(oauthHeaders["oauth_consumer_key"], clientCredentials.first); |
| QCOMPARE(oauthHeaders["oauth_version"], "1.0"); |
| QString expectedSignature; |
| { |
| QVariantMap modifiedHeaders = oauthHeaders.unite(parameters); |
| modifiedHeaders.remove("oauth_signature"); |
| QOAuth1Signature signature(url, |
| clientCredentials.second, |
| token.second, |
| static_cast<QOAuth1Signature::HttpRequestMethod>(requestType), |
| modifiedHeaders); |
| switch (signatureMethod) { |
| case QOAuth1::SignatureMethod::PlainText: |
| expectedSignature = signature.plainText(); |
| break; |
| case QOAuth1::SignatureMethod::Hmac_Sha1: |
| expectedSignature = signature.hmacSha1().toBase64(); |
| break; |
| case QOAuth1::SignatureMethod::Rsa_Sha1: |
| expectedSignature = signature.rsaSha1(); |
| break; |
| } |
| } |
| QCOMPARE(oauthHeaders["oauth_signature"], expectedSignature); |
| } |
| |
| void tst_OAuth1::prepareRequestSignature_data() |
| { |
| QTest::addColumn<QString>("consumerKey"); |
| QTest::addColumn<QString>("consumerSecret"); |
| QTest::addColumn<QString>("accessKey"); |
| QTest::addColumn<QString>("accessKeySecret"); |
| QTest::addColumn<QNetworkRequest>("request"); |
| QTest::addColumn<QByteArray>("operation"); |
| QTest::addColumn<QByteArray>("body"); |
| QTest::addColumn<QVariantMap>("extraParams"); |
| |
| QTest::newRow("get_simple") |
| << "key" |
| << "secret" |
| << "accesskey" |
| << "accesssecret" |
| << QNetworkRequest(QUrl("http://term.ie/oauth/example/echo_api.php")) |
| << QByteArray("GET") |
| << QByteArray() |
| << QVariantMap(); |
| |
| QTest::newRow("get_params") |
| << "key" |
| << "secret" |
| << "accesskey" |
| << "accesssecret" |
| << QNetworkRequest(QUrl("http://term.ie/oauth/example/echo_api.php?" |
| "first=first&second=second")) |
| << QByteArray("GET") |
| << QByteArray() |
| << QVariantMap(); |
| |
| QNetworkRequest postRequest(QUrl("http://term.ie/oauth/example/echo_api.php")); |
| postRequest.setHeader(QNetworkRequest::ContentTypeHeader, |
| QByteArray("application/x-www-form-urlencoded")); |
| QTest::newRow("post_params") |
| << "key" |
| << "secret" |
| << "accesskey" |
| << "accesssecret" |
| << postRequest |
| << QByteArray("POST") |
| << QByteArray("first=first&second=second") |
| << QVariantMap({ |
| {"first", "first"}, |
| {"second", "second"} |
| }); |
| |
| QTest::newRow("patch_param") |
| << "key" |
| << "secret" |
| << "accesskey" |
| << "accesssecret" |
| << QNetworkRequest(QUrl("http://term.ie/oauth/example/echo_api.php?" |
| "first=first&second=second")) |
| << QByteArray("PATCH") |
| << QByteArray() |
| << QVariantMap(); |
| } |
| |
| void tst_OAuth1::prepareRequestSignature() |
| { |
| QFETCH(QString, consumerKey); |
| QFETCH(QString, consumerSecret); |
| QFETCH(QString, accessKey); |
| QFETCH(QString, accessKeySecret); |
| QFETCH(QNetworkRequest, request); |
| QFETCH(QByteArray, operation); |
| QFETCH(QByteArray, body); |
| QFETCH(QVariantMap, extraParams); |
| |
| QOAuth1 o1; |
| o1.setClientCredentials(consumerKey, consumerSecret); |
| o1.setTokenCredentials(accessKey, accessKeySecret); |
| |
| o1.prepareRequest(&request, operation, body); |
| |
| // extract oauth parameters from the headers |
| QVariantMap authArgs; |
| const auto authHeader = request.rawHeader("Authorization"); |
| QCOMPARE(authHeader.mid(0, 6), "OAuth "); |
| const auto values = authHeader.mid(6).split(','); |
| for (const auto &pair : values) { |
| const auto argPair = pair.split('='); |
| QCOMPARE(argPair.size(), 2); |
| QCOMPARE(argPair[1].front(), '\"'); |
| QCOMPARE(argPair[1].back(), '\"'); |
| authArgs.insert(argPair[0], argPair[1].mid(1, argPair[1].size() - 2)); |
| } |
| |
| //compare known parameters |
| QCOMPARE(authArgs.value(oauthConsumerKey).toByteArray(), consumerKey); |
| QCOMPARE(authArgs.value(oauthToken).toByteArray(), accessKey); |
| QCOMPARE(authArgs.value(oauthSignatureMethod).toByteArray(), QByteArray("HMAC-SHA1")); |
| QCOMPARE(authArgs.value(oauthVersion).toByteArray(), QByteArray("1.0")); |
| QVERIFY(authArgs.contains(oauthNonce)); |
| QVERIFY(authArgs.contains(oauthTimestamp)); |
| QVERIFY(authArgs.contains(oauthSignature)); |
| |
| // verify the signature |
| const auto sigString = QUrl::fromPercentEncoding(authArgs.take(oauthSignature) |
| .toByteArray()).toUtf8(); |
| QOAuth1Signature signature(request.url(), |
| consumerSecret, |
| accessKeySecret, |
| QOAuth1Signature::HttpRequestMethod::Custom, |
| authArgs.unite(extraParams)); |
| signature.setCustomMethodString(operation); |
| const auto signatureData = signature.hmacSha1(); |
| QCOMPARE(signatureData.toBase64(), sigString); |
| } |
| |
| void tst_OAuth1::grant_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"); |
| |
| if (hostReachable(QLatin1String("term.ie"))) { |
| 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; |
| } |
| } |
| |
| void tst_OAuth1::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 clientIdentifierSpy(&o1, &QOAuth1::clientIdentifierChanged); |
| QSignalSpy clientSharedSecretSpy(&o1, &QOAuth1::clientSharedSecretChanged); |
| o1.setClientCredentials(consumerKey, consumerSecret); |
| QCOMPARE(clientIdentifierSpy.count(), 1); |
| QCOMPARE(clientSharedSecretSpy.count(), 1); |
| } |
| { |
| QSignalSpy spy(&o1, &QOAuth1::temporaryCredentialsUrlChanged); |
| o1.setTemporaryCredentialsUrl(requestTokenUrl); |
| QCOMPARE(spy.count(), 1); |
| } |
| { |
| QSignalSpy spy(&o1, &QOAuth1::tokenCredentialsUrlChanged); |
| o1.setTokenCredentialsUrl(accessTokenUrl); |
| QCOMPARE(spy.count(), 1); |
| } |
| connect(&o1, &QAbstractOAuth::statusChanged, [&](QAbstractOAuth::Status status) { |
| if (status == QAbstractOAuth::Status::TemporaryCredentialsReceived) { |
| if (!requestToken.isEmpty()) |
| QCOMPARE(requestToken, o1.tokenCredentials().first); |
| if (!requestTokenSecret.isEmpty()) |
| QCOMPARE(requestTokenSecret, o1.tokenCredentials().second); |
| tokenReceived = true; |
| } else if (status == QAbstractOAuth::Status::Granted) { |
| if (!accessToken.isEmpty()) |
| QCOMPARE(accessToken, o1.tokenCredentials().first); |
| if (!accessTokenSecret.isEmpty()) |
| QCOMPARE(accessTokenSecret, o1.tokenCredentials().second); |
| tokenReceived = true; |
| } |
| }); |
| |
| QEventLoop eventLoop; |
| |
| QSignalSpy grantSignalSpy(&o1, &QOAuth1::granted); |
| QTimer::singleShot(10000, &eventLoop, &QEventLoop::quit); |
| connect(&o1, &QOAuth1::granted, &eventLoop, &QEventLoop::quit); |
| o1.grant(); |
| eventLoop.exec(); |
| QVERIFY(tokenReceived); |
| QCOMPARE(grantSignalSpy.count(), 1); |
| QCOMPARE(o1.status(), QAbstractOAuth::Status::Granted); |
| } |
| |
| void tst_OAuth1::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") }, |
| { QStringLiteral("c2&a3"), QStringLiteral("2=%$&@q") } |
| }; |
| |
| if (hostReachable(QLatin1String("term.ie"))) { |
| 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("term.ie_percent_encoded_query") |
| << "key" |
| << "secret" |
| << "accesskey" |
| << "accesssecret" |
| << QUrl("http://term.ie/oauth/example/echo_api.php?key=%40value+1%2B2=3") |
| << parameters |
| << QNetworkAccessManager::GetOperation; |
| } else { |
| QSKIP("Skipping test due to unreacahble term.ie host"); |
| } |
| } |
| |
| void tst_OAuth1::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; |
| { |
| if (url.hasQuery()) { |
| parametersString = url.query(QUrl::FullyDecoded); |
| if (!parameters.empty()) |
| parametersString.append(QLatin1Char('&')); |
| } |
| 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.setClientCredentials(consumerKey, consumerSecret); |
| o1.setTokenCredentials(accessKey, accessKeySecret); |
| if (operation == QNetworkAccessManager::GetOperation) |
| reply.reset(o1.get(url, parameters)); |
| else if (operation == QNetworkAccessManager::PostOperation) |
| reply.reset(o1.post(url, parameters)); |
| QVERIFY(!reply.isNull()); |
| QVERIFY(!reply->isFinished()); |
| |
| connect(&networkAccessManager, &QNetworkAccessManager::finished, |
| [&](QNetworkReply *reply) { |
| QByteArray data = reply->readAll(); |
| QUrlQuery query(QString::fromUtf8(data)); |
| receivedData = query.toString(QUrl::FullyDecoded); |
| }); |
| QVERIFY(waitForFinish(reply) == Success); |
| QCOMPARE(receivedData, parametersString); |
| reply.clear(); |
| } |
| |
| void tst_OAuth1::prepareRequestCalls_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<QByteArray>("operation"); |
| |
| const QVariantMap parameters { { QStringLiteral("first"), QStringLiteral("first") }, |
| { QStringLiteral("second"), QStringLiteral("second") }, |
| { QStringLiteral("third"), QStringLiteral("third") }, |
| { QStringLiteral("c2&a3"), QStringLiteral("2=%$&@q") } |
| }; |
| |
| if (hostReachable(QLatin1String("term.ie"))) { |
| QTest::newRow("term.ie_get") << "key" |
| << "secret" |
| << "accesskey" |
| << "accesssecret" |
| << QUrl("http://term.ie/oauth/example/echo_api.php") |
| << parameters |
| << QByteArray("GET"); |
| QTest::newRow("term.ie_post") << "key" |
| << "secret" |
| << "accesskey" |
| << "accesssecret" |
| << QUrl("http://term.ie/oauth/example/echo_api.php") |
| << parameters |
| << QByteArray("POST"); |
| QTest::newRow("term.ie_percent_encoded_query") |
| << "key" |
| << "secret" |
| << "accesskey" |
| << "accesssecret" |
| << QUrl("http://term.ie/oauth/example/echo_api.php?key=%40value+1%2B2=3") |
| << parameters |
| << QByteArray("GET"); |
| } else { |
| QSKIP("Skipping test due to unreacahble term.ie host"); |
| } |
| } |
| |
| void tst_OAuth1::prepareRequestCalls() |
| { |
| QFETCH(QString, consumerKey); |
| QFETCH(QString, consumerSecret); |
| QFETCH(QString, accessKey); |
| QFETCH(QString, accessKeySecret); |
| QFETCH(QUrl, url); |
| QFETCH(QVariantMap, parameters); |
| QFETCH(QByteArray, operation); |
| |
| QNetworkAccessManager networkAccessManager; |
| QNetworkReplyPtr reply; |
| QString receivedData; |
| QString parametersString; |
| { |
| if (url.hasQuery()) { |
| parametersString = url.query(QUrl::FullyDecoded); |
| if (!parameters.empty()) |
| parametersString.append(QLatin1Char('&')); |
| } |
| 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.setClientCredentials(consumerKey, consumerSecret); |
| o1.setTokenCredentials(accessKey, accessKeySecret); |
| |
| if (operation != "POST") { |
| QUrlQuery query(url.query()); |
| for (auto it = parameters.begin(), end = parameters.end(); it != end; ++it) |
| query.addQueryItem(it.key(), it.value().toString()); |
| url.setQuery(query); |
| } |
| QNetworkRequest request(url); |
| QByteArray body; |
| if (operation == "POST") { |
| QUrlQuery query(url.query()); |
| for (auto it = parameters.begin(), end = parameters.end(); it != end; ++it) |
| query.addQueryItem(it.key(), it.value().toString()); |
| body = query.toString().toUtf8(); |
| request.setHeader(QNetworkRequest::ContentTypeHeader, |
| QByteArray("application/x-www-form-urlencoded")); |
| } |
| |
| o1.prepareRequest(&request, operation, body); |
| if (body.isEmpty()) |
| reply.reset(o1.networkAccessManager()->sendCustomRequest(request, operation)); |
| else |
| reply.reset(o1.networkAccessManager()->sendCustomRequest(request, operation, body)); |
| QVERIFY(!reply.isNull()); |
| QVERIFY(!reply->isFinished()); |
| |
| connect(&networkAccessManager, &QNetworkAccessManager::finished, |
| [&](QNetworkReply *reply) { |
| QByteArray data = reply->readAll(); |
| QUrlQuery query(QString::fromUtf8(data)); |
| receivedData = query.toString(QUrl::FullyDecoded); |
| }); |
| QVERIFY(waitForFinish(reply) == Success); |
| QCOMPARE(receivedData, parametersString); |
| reply.clear(); |
| } |
| |
| void tst_OAuth1::secondTemporaryToken() |
| { |
| QNetworkAccessManager networkAccessManager; |
| |
| const StringPair expectedToken(qMakePair(QStringLiteral("temporaryKey"), QStringLiteral("temporaryToken"))); |
| WebServer webServer([&](const WebServer::HttpRequest &request, QTcpSocket *socket) { |
| Q_UNUSED(request); |
| const QString format = "oauth_token=%1&oauth_token_secret=%2&oauth_callback_confirmed=true"; |
| const QByteArray text = format.arg(expectedToken.first, expectedToken.second).toUtf8(); |
| const QByteArray replyMessage { |
| "HTTP/1.0 200 OK\r\n" |
| "Content-Type: application/x-www-form-urlencoded; charset=\"utf-8\"\r\n" |
| "Content-Length: " + QByteArray::number(text.size()) + "\r\n\r\n" |
| + text |
| }; |
| socket->write(replyMessage); |
| }); |
| |
| QOAuth1 o1(&networkAccessManager); |
| |
| StringPair clientCredentials = qMakePair(QStringLiteral("user"), QStringLiteral("passwd")); |
| o1.setClientCredentials(clientCredentials); |
| o1.setTemporaryCredentialsUrl(webServer.url(QStringLiteral("temporary"))); |
| o1.setAuthorizationUrl(webServer.url(QStringLiteral("authorization"))); |
| o1.setTokenCredentialsUrl(webServer.url(QStringLiteral("token"))); |
| |
| StringPair tokenReceived; |
| connect(&o1, &QOAuth1::tokenChanged, [&tokenReceived](const QString &token) { |
| tokenReceived.first = token; |
| }); |
| bool replyReceived = false; |
| connect(&o1, &QOAuth1::tokenSecretChanged, [&tokenReceived, &replyReceived](const QString &tokenSecret) { |
| tokenReceived.second = tokenSecret; |
| replyReceived = true; |
| }); |
| |
| o1.grant(); |
| QTRY_VERIFY(replyReceived); |
| |
| QVERIFY(!tokenReceived.first.isEmpty()); |
| QVERIFY(!tokenReceived.second.isEmpty()); |
| QCOMPARE(o1.status(), QAbstractOAuth::Status::TemporaryCredentialsReceived); |
| QCOMPARE(tokenReceived, expectedToken); |
| |
| replyReceived = false; // reset this so we can 'synchronize' on it again |
| // Do the same request again, should end up in the same state!! |
| o1.grant(); |
| QTRY_VERIFY(replyReceived); |
| |
| QVERIFY(!tokenReceived.first.isEmpty()); |
| QVERIFY(!tokenReceived.second.isEmpty()); |
| QCOMPARE(o1.status(), QAbstractOAuth::Status::TemporaryCredentialsReceived); |
| QCOMPARE(tokenReceived, expectedToken); |
| } |
| |
| QTEST_MAIN(tst_OAuth1) |
| #include "tst_oauth1.moc" |