| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2017 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the test suite of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| 21 | ** included in the packaging of this file. Please review the following |
| 22 | ** information to ensure the GNU General Public License requirements will |
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| 24 | ** |
| 25 | ** $QT_END_LICENSE$ |
| 26 | ** |
| 27 | ****************************************************************************/ |
| 28 | |
| 29 | #include <QtCore> |
| 30 | #include <QtTest> |
| 31 | #include <QtNetwork> |
| 32 | #include <QHostInfo> |
| 33 | |
| 34 | #include <QtNetworkAuth/qoauth1.h> |
| 35 | #include <QtNetworkAuth/qoauth1signature.h> |
| 36 | |
| 37 | #include <private/qoauth1_p.h> |
| 38 | |
| 39 | #include "webserver.h" |
| 40 | |
| 41 | Q_DECLARE_METATYPE(QNetworkAccessManager::Operation) |
| 42 | Q_DECLARE_METATYPE(QAbstractOAuth::Error) |
| 43 | |
| 44 | // TODO: Test PUT and DELETE operations. |
| 45 | // TODO: Write tests to test errors. |
| 46 | // TODO: Remove common event loop |
| 47 | |
| 48 | typedef QSharedPointer<QNetworkReply> QNetworkReplyPtr; |
| 49 | |
| 50 | class tst_OAuth1 : public QObject |
| 51 | { |
| 52 | Q_OBJECT |
| 53 | |
| 54 | using StringPair = QPair<QString, QString>; |
| 55 | |
| 56 | QEventLoop *loop = nullptr; |
| 57 | enum RunSimpleRequestReturn { Timeout = 0, Success, Failure }; |
| 58 | int returnCode; |
| 59 | |
| 60 | using QObject::connect; |
| 61 | static bool connect(const QNetworkReplyPtr &ptr, |
| 62 | const char *signal, |
| 63 | const QObject *receiver, |
| 64 | const char *slot, |
| 65 | Qt::ConnectionType ct = Qt::AutoConnection) |
| 66 | { |
| 67 | return connect(sender: ptr.data(), signal, receiver, member: slot, ct); |
| 68 | } |
| 69 | bool connect(const QNetworkReplyPtr &ptr, |
| 70 | const char *signal, |
| 71 | const char *slot, |
| 72 | Qt::ConnectionType ct = Qt::AutoConnection) |
| 73 | { |
| 74 | return connect(asender: ptr.data(), asignal: signal, amember: slot, atype: ct); |
| 75 | } |
| 76 | |
| 77 | public: |
| 78 | int waitForFinish(QNetworkReplyPtr &reply); |
| 79 | void fillParameters(QVariantMap *parameters, const QUrlQuery &query); |
| 80 | |
| 81 | template<class Type> |
| 82 | struct PropertyTester |
| 83 | { |
| 84 | typedef Type InnerType; |
| 85 | typedef void(QOAuth1::*ConstSignalType)(const Type &); |
| 86 | typedef void(QOAuth1::*SignalType)(Type); |
| 87 | typedef QVector<std::function<void(Type *, QOAuth1 *object)>> SetterFunctions; |
| 88 | |
| 89 | private: |
| 90 | // Each entry in setters should set its first parameter to an expected value |
| 91 | // and act on its second, a QOAuth1 object, to trigger signal; this |
| 92 | // function shall check that signal is passed the value the setter previously |
| 93 | // told us to expect. |
| 94 | template<class SignalType> |
| 95 | static void runImpl(SignalType signal, const SetterFunctions &setters) |
| 96 | { |
| 97 | QOAuth1 obj; |
| 98 | Type expectedValue; |
| 99 | QSignalSpy spy(&obj, signal); |
| 100 | connect(&obj, signal, [&](const Type &value) { |
| 101 | QCOMPARE(expectedValue, value); |
| 102 | }); |
| 103 | for (const auto &setter : setters) { |
| 104 | const auto previous = expectedValue; |
| 105 | setter(&expectedValue, &obj); |
| 106 | QVERIFY(previous != expectedValue); // To check if the value was modified |
| 107 | } |
| 108 | QCOMPARE(spy.count(), setters.size()); // The signal should be emitted |
| 109 | } |
| 110 | |
| 111 | public: |
| 112 | |
| 113 | static void run(ConstSignalType signal, const SetterFunctions &setters) |
| 114 | { |
| 115 | runImpl(signal, setters); |
| 116 | } |
| 117 | |
| 118 | static void run(SignalType signal, const SetterFunctions &setters) |
| 119 | { |
| 120 | runImpl(signal, setters); |
| 121 | } |
| 122 | }; |
| 123 | |
| 124 | QVariantMap parseAuthorizationString(const QString &string) |
| 125 | { |
| 126 | const QString prefix = QStringLiteral("OAuth " ); |
| 127 | QVariantMap ret; |
| 128 | Q_ASSERT(string.startsWith(prefix)); |
| 129 | QRegularExpression rx("(?<key>.[^=]*)=\"(?<value>.[^\"]*)\",?" ); |
| 130 | auto globalMatch = rx.globalMatch(subject: string, offset: prefix.size()); |
| 131 | while (globalMatch.hasNext()) { |
| 132 | const auto match = globalMatch.next(); |
| 133 | auto key = match.captured(name: "key" ); |
| 134 | QString value = match.captured(name: "value" ); |
| 135 | value = QString::fromUtf8(str: QByteArray::fromPercentEncoding(pctEncoded: value.toUtf8())); |
| 136 | ret.insert(key, value); |
| 137 | } |
| 138 | return ret; |
| 139 | } |
| 140 | |
| 141 | public Q_SLOTS: |
| 142 | void finished(); |
| 143 | void gotError(); |
| 144 | |
| 145 | private Q_SLOTS: |
| 146 | void clientIdentifierSignal(); |
| 147 | void clientSharedSecretSignal(); |
| 148 | void tokenSignal(); |
| 149 | void tokenSecretSignal(); |
| 150 | void temporaryCredentialsUrlSignal(); |
| 151 | void temporaryTokenCredentialsUrlSignal(); |
| 152 | void tokenCredentialsUrlSignal(); |
| 153 | void signatureMethodSignal(); |
| 154 | |
| 155 | void getToken_data(); |
| 156 | void getToken(); |
| 157 | |
| 158 | void prepareRequestSignature_data(); |
| 159 | void prepareRequestSignature(); |
| 160 | |
| 161 | void grant_data(); |
| 162 | void grant(); |
| 163 | |
| 164 | void authenticatedCalls_data(); |
| 165 | void authenticatedCalls(); |
| 166 | |
| 167 | void prepareRequestCalls_data(); |
| 168 | void prepareRequestCalls(); |
| 169 | |
| 170 | void secondTemporaryToken(); |
| 171 | }; |
| 172 | |
| 173 | const auto oauthVersion = QStringLiteral("oauth_version" ); |
| 174 | const auto oauthConsumerKey = QStringLiteral("oauth_consumer_key" ); |
| 175 | const auto oauthNonce = QStringLiteral("oauth_nonce" ); |
| 176 | const auto oauthSignatureMethod = QStringLiteral("oauth_signature_method" ); |
| 177 | const auto oauthTimestamp = QStringLiteral("oauth_timestamp" ); |
| 178 | const auto oauthToken = QStringLiteral("oauth_token" ); |
| 179 | const auto oauthSignature = QStringLiteral("oauth_signature" ); |
| 180 | |
| 181 | bool hostReachable(const QLatin1String &host) |
| 182 | { |
| 183 | // check host exists |
| 184 | QHostInfo hostInfo = QHostInfo::fromName(name: host); |
| 185 | if (hostInfo.error() != QHostInfo::NoError) |
| 186 | return false; |
| 187 | |
| 188 | // try to connect to host |
| 189 | QTcpSocket socket; |
| 190 | socket.connectToHost(hostName: host, port: 80); |
| 191 | if (!socket.waitForConnected(msecs: 1000)) |
| 192 | return false; |
| 193 | |
| 194 | return true; |
| 195 | } |
| 196 | |
| 197 | int tst_OAuth1::waitForFinish(QNetworkReplyPtr &reply) |
| 198 | { |
| 199 | int count = 0; |
| 200 | |
| 201 | connect(ptr: reply, SIGNAL(finished()), SLOT(finished())); |
| 202 | connect(ptr: reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(gotError())); |
| 203 | returnCode = Success; |
| 204 | loop = new QEventLoop; |
| 205 | QSignalSpy spy(reply.data(), SIGNAL(downloadProgress(qint64,qint64))); |
| 206 | while (!reply->isFinished()) { |
| 207 | QTimer::singleShot(msec: 5000, receiver: loop, SLOT(quit())); |
| 208 | if (loop->exec() == Timeout && count == spy.count() && !reply->isFinished()) { |
| 209 | returnCode = Timeout; |
| 210 | break; |
| 211 | } |
| 212 | count = spy.count(); |
| 213 | } |
| 214 | delete loop; |
| 215 | loop = nullptr; |
| 216 | |
| 217 | return returnCode; |
| 218 | } |
| 219 | |
| 220 | void tst_OAuth1::fillParameters(QVariantMap *parameters, const QUrlQuery &query) |
| 221 | { |
| 222 | const auto list = query.queryItems(); |
| 223 | for (auto it = list.begin(), end = list.end(); it != end; ++it) |
| 224 | parameters->insert(key: it->first, value: it->second); |
| 225 | } |
| 226 | |
| 227 | void tst_OAuth1::finished() |
| 228 | { |
| 229 | if (loop) |
| 230 | loop->exit(returnCode: returnCode = Success); |
| 231 | } |
| 232 | |
| 233 | void tst_OAuth1::gotError() |
| 234 | { |
| 235 | if (loop) |
| 236 | loop->exit(returnCode: returnCode = Failure); |
| 237 | disconnect(sender: QObject::sender(), SIGNAL(finished()), receiver: this, member: nullptr); |
| 238 | } |
| 239 | |
| 240 | void tst_OAuth1::clientIdentifierSignal() |
| 241 | { |
| 242 | using PropertyTester = PropertyTester<QString>; |
| 243 | PropertyTester::SetterFunctions setters { |
| 244 | [](QString *expectedValue, QOAuth1 *object) { |
| 245 | *expectedValue = "setClientIdentifier" ; |
| 246 | object->setClientIdentifier(*expectedValue); |
| 247 | }, |
| 248 | [](QString *expectedValue, QOAuth1 *object) { |
| 249 | *expectedValue = "setClientCredentials" ; |
| 250 | object->setClientCredentials(qMakePair(x: *expectedValue, y: QString())); |
| 251 | } |
| 252 | }; |
| 253 | PropertyTester::run(signal: &QOAuth1::clientIdentifierChanged, setters); |
| 254 | } |
| 255 | |
| 256 | void tst_OAuth1::clientSharedSecretSignal() |
| 257 | { |
| 258 | using PropertyTester = PropertyTester<QString>; |
| 259 | PropertyTester::SetterFunctions setters { |
| 260 | [](QString *expectedValue, QOAuth1 *object) { |
| 261 | *expectedValue = "setClientSharedSecret" ; |
| 262 | object->setClientSharedSecret(*expectedValue); |
| 263 | }, |
| 264 | [](QString *expectedValue, QOAuth1 *object) { |
| 265 | *expectedValue = "setClientCredentials" ; |
| 266 | object->setClientCredentials(qMakePair(x: QString(), y: *expectedValue)); |
| 267 | } |
| 268 | }; |
| 269 | PropertyTester::run(signal: &QOAuth1::clientSharedSecretChanged, setters); |
| 270 | } |
| 271 | |
| 272 | void tst_OAuth1::tokenSignal() |
| 273 | { |
| 274 | using PropertyTester = PropertyTester<QString>; |
| 275 | PropertyTester::SetterFunctions setters { |
| 276 | [](QString *expectedValue, QOAuth1 *object) { |
| 277 | *expectedValue = "setToken" ; |
| 278 | object->setToken(*expectedValue); |
| 279 | }, |
| 280 | [](QString *expectedValue, QOAuth1 *object) { |
| 281 | *expectedValue = "setTokenCredentials" ; |
| 282 | object->setTokenCredentials(qMakePair(x: *expectedValue, y: QString())); |
| 283 | } |
| 284 | }; |
| 285 | PropertyTester::run(signal: &QOAuth1::tokenChanged, setters); |
| 286 | } |
| 287 | |
| 288 | void tst_OAuth1::tokenSecretSignal() |
| 289 | { |
| 290 | using PropertyTester = PropertyTester<QString>; |
| 291 | PropertyTester::SetterFunctions setters { |
| 292 | [](QString *expectedValue, QOAuth1 *object) { |
| 293 | *expectedValue = "setTokenSecret" ; |
| 294 | object->setTokenSecret(*expectedValue); |
| 295 | }, |
| 296 | [](QString *expectedValue, QOAuth1 *object) { |
| 297 | *expectedValue = "setTokenCredentials" ; |
| 298 | object->setTokenCredentials(qMakePair(x: QString(), y: *expectedValue)); |
| 299 | } |
| 300 | }; |
| 301 | PropertyTester::run(signal: &QOAuth1::tokenSecretChanged, setters); |
| 302 | } |
| 303 | |
| 304 | void tst_OAuth1::temporaryCredentialsUrlSignal() |
| 305 | { |
| 306 | using PropertyTester = PropertyTester<QUrl>; |
| 307 | PropertyTester::SetterFunctions setters { |
| 308 | [](QUrl *expectedValue, QOAuth1 *object) { |
| 309 | *expectedValue = QUrl("http://example.net/" ); |
| 310 | object->setTemporaryCredentialsUrl(*expectedValue); |
| 311 | } |
| 312 | }; |
| 313 | PropertyTester::run(signal: &QOAuth1::temporaryCredentialsUrlChanged, setters); |
| 314 | } |
| 315 | |
| 316 | void tst_OAuth1::temporaryTokenCredentialsUrlSignal() |
| 317 | { |
| 318 | using PropertyTester = PropertyTester<QUrl>; |
| 319 | PropertyTester::SetterFunctions setters { |
| 320 | [](QUrl *expectedValue, QOAuth1 *object) { |
| 321 | *expectedValue = QUrl("http://example.net/" ); |
| 322 | object->setTemporaryCredentialsUrl(*expectedValue); |
| 323 | } |
| 324 | }; |
| 325 | PropertyTester::run(signal: &QOAuth1::temporaryCredentialsUrlChanged, setters); |
| 326 | } |
| 327 | |
| 328 | void tst_OAuth1::tokenCredentialsUrlSignal() |
| 329 | { |
| 330 | using PropertyTester = PropertyTester<QUrl>; |
| 331 | PropertyTester::SetterFunctions setters { |
| 332 | [](QUrl *expectedValue, QOAuth1 *object) { |
| 333 | *expectedValue = QUrl("http://example.net/" ); |
| 334 | object->setTokenCredentialsUrl(*expectedValue); |
| 335 | } |
| 336 | }; |
| 337 | PropertyTester::run(signal: &QOAuth1::tokenCredentialsUrlChanged, setters); |
| 338 | } |
| 339 | |
| 340 | void tst_OAuth1::signatureMethodSignal() |
| 341 | { |
| 342 | using PropertyTester = PropertyTester<QOAuth1::SignatureMethod>; |
| 343 | PropertyTester::SetterFunctions setters { |
| 344 | [](PropertyTester::InnerType *expectedValue, QOAuth1 *object) { |
| 345 | QVERIFY(object->signatureMethod() != QOAuth1::SignatureMethod::PlainText); |
| 346 | *expectedValue = QOAuth1::SignatureMethod::PlainText; |
| 347 | object->setSignatureMethod(*expectedValue); |
| 348 | } |
| 349 | }; |
| 350 | PropertyTester::run(signal: &QOAuth1::signatureMethodChanged, setters); |
| 351 | } |
| 352 | |
| 353 | void tst_OAuth1::getToken_data() |
| 354 | { |
| 355 | QTest::addColumn<StringPair>(name: "clientCredentials" ); |
| 356 | QTest::addColumn<StringPair>(name: "token" ); |
| 357 | QTest::addColumn<StringPair>(name: "expectedToken" ); |
| 358 | QTest::addColumn<QOAuth1::SignatureMethod>(name: "signatureMethod" ); |
| 359 | QTest::addColumn<QNetworkAccessManager::Operation>(name: "requestType" ); |
| 360 | |
| 361 | const StringPair emptyCredentials; |
| 362 | QTest::newRow(dataTag: "temporary_get_plainText" ) |
| 363 | << qMakePair(QStringLiteral("key" ), QStringLiteral("secret" )) |
| 364 | << emptyCredentials |
| 365 | << qMakePair(QStringLiteral("requestkey" ), QStringLiteral("requestsecret" )) |
| 366 | << QOAuth1::SignatureMethod::PlainText |
| 367 | << QNetworkAccessManager::GetOperation; |
| 368 | |
| 369 | QTest::newRow(dataTag: "temporary_get_hmacSha1" ) |
| 370 | << qMakePair(QStringLiteral("key" ), QStringLiteral("secret" )) |
| 371 | << emptyCredentials |
| 372 | << qMakePair(QStringLiteral("requestkey" ), QStringLiteral("requestsecret" )) |
| 373 | << QOAuth1::SignatureMethod::Hmac_Sha1 |
| 374 | << QNetworkAccessManager::GetOperation; |
| 375 | |
| 376 | QTest::newRow(dataTag: "temporary_post_plainText" ) |
| 377 | << qMakePair(QStringLiteral("key" ), QStringLiteral("secret" )) |
| 378 | << emptyCredentials |
| 379 | << qMakePair(QStringLiteral("requestkey" ), QStringLiteral("requestsecret" )) |
| 380 | << QOAuth1::SignatureMethod::PlainText |
| 381 | << QNetworkAccessManager::PostOperation; |
| 382 | |
| 383 | QTest::newRow(dataTag: "temporary_post_hmacSha1" ) |
| 384 | << qMakePair(QStringLiteral("key" ), QStringLiteral("secret" )) |
| 385 | << emptyCredentials |
| 386 | << qMakePair(QStringLiteral("requestkey" ), QStringLiteral("requestsecret" )) |
| 387 | << QOAuth1::SignatureMethod::Hmac_Sha1 |
| 388 | << QNetworkAccessManager::PostOperation; |
| 389 | |
| 390 | QTest::newRow(dataTag: "token_get_plainText" ) |
| 391 | << qMakePair(QStringLiteral("key" ), QStringLiteral("secret" )) |
| 392 | << qMakePair(QStringLiteral("requestkey" ), QStringLiteral("requestsecret" )) |
| 393 | << qMakePair(QStringLiteral("accesskey" ), QStringLiteral("accesssecret" )) |
| 394 | << QOAuth1::SignatureMethod::PlainText |
| 395 | << QNetworkAccessManager::GetOperation; |
| 396 | |
| 397 | QTest::newRow(dataTag: "token_get_hmacSha1" ) |
| 398 | << qMakePair(QStringLiteral("key" ), QStringLiteral("secret" )) |
| 399 | << qMakePair(QStringLiteral("requestkey" ), QStringLiteral("requestsecret" )) |
| 400 | << qMakePair(QStringLiteral("accesskey" ), QStringLiteral("accesssecret" )) |
| 401 | << QOAuth1::SignatureMethod::Hmac_Sha1 |
| 402 | << QNetworkAccessManager::GetOperation; |
| 403 | |
| 404 | QTest::newRow(dataTag: "token_post_plainText" ) |
| 405 | << qMakePair(QStringLiteral("key" ), QStringLiteral("secret" )) |
| 406 | << qMakePair(QStringLiteral("requestkey" ), QStringLiteral("requestsecret" )) |
| 407 | << qMakePair(QStringLiteral("accesskey" ), QStringLiteral("accesssecret" )) |
| 408 | << QOAuth1::SignatureMethod::PlainText |
| 409 | << QNetworkAccessManager::PostOperation; |
| 410 | |
| 411 | QTest::newRow(dataTag: "token_post_hmacSha1" ) |
| 412 | << qMakePair(QStringLiteral("key" ), QStringLiteral("secret" )) |
| 413 | << qMakePair(QStringLiteral("requestkey" ), QStringLiteral("requestsecret" )) |
| 414 | << qMakePair(QStringLiteral("accesskey" ), QStringLiteral("accesssecret" )) |
| 415 | << QOAuth1::SignatureMethod::Hmac_Sha1 |
| 416 | << QNetworkAccessManager::PostOperation; |
| 417 | } |
| 418 | |
| 419 | void tst_OAuth1::getToken() |
| 420 | { |
| 421 | QFETCH(StringPair, clientCredentials); |
| 422 | QFETCH(StringPair, token); |
| 423 | QFETCH(StringPair, expectedToken); |
| 424 | QFETCH(QOAuth1::SignatureMethod, signatureMethod); |
| 425 | QFETCH(QNetworkAccessManager::Operation, requestType); |
| 426 | |
| 427 | StringPair tokenReceived; |
| 428 | QNetworkAccessManager networkAccessManager; |
| 429 | QNetworkReplyPtr reply; |
| 430 | QVariantMap ; |
| 431 | |
| 432 | WebServer webServer([&](const WebServer::HttpRequest &request, QTcpSocket *socket) { |
| 433 | oauthHeaders = parseAuthorizationString(string: request.headers["Authorization" ]); |
| 434 | const QString format = "oauth_token=%1&oauth_token_secret=%2" ; |
| 435 | const QByteArray text = format.arg(args&: expectedToken.first, args&: expectedToken.second).toUtf8(); |
| 436 | const QByteArray replyMessage { |
| 437 | "HTTP/1.0 200 OK\r\n" |
| 438 | "Content-Type: application/x-www-form-urlencoded; charset=\"utf-8\"\r\n" |
| 439 | "Content-Length: " + QByteArray::number(text.size()) + "\r\n\r\n" |
| 440 | + text |
| 441 | }; |
| 442 | socket->write(data: replyMessage); |
| 443 | }); |
| 444 | |
| 445 | struct OAuth1 : QOAuth1 |
| 446 | { |
| 447 | OAuth1(QNetworkAccessManager *manager) : QOAuth1(manager) {} |
| 448 | using QOAuth1::requestTokenCredentials; |
| 449 | } o1(&networkAccessManager); |
| 450 | const auto url = webServer.url(QStringLiteral("token" )); |
| 451 | |
| 452 | o1.setSignatureMethod(signatureMethod); |
| 453 | o1.setClientCredentials(clientIdentifier: clientCredentials.first, clientSharedSecret: clientCredentials.second); |
| 454 | o1.setTokenCredentials(token); |
| 455 | o1.setTemporaryCredentialsUrl(url); |
| 456 | QVariantMap parameters {{ "c2&a3" , "c2=a3" }}; |
| 457 | reply.reset(t: o1.requestTokenCredentials(operation: requestType, url, temporaryToken: token, parameters)); |
| 458 | QVERIFY(!reply.isNull()); |
| 459 | connect(sender: &o1, signal: &QOAuth1::tokenChanged, slot: [&tokenReceived](const QString &token){ |
| 460 | tokenReceived.first = token; |
| 461 | }); |
| 462 | connect(sender: &o1, signal: &QOAuth1::tokenSecretChanged, slot: [&tokenReceived](const QString &tokenSecret) { |
| 463 | tokenReceived.second = tokenSecret; |
| 464 | }); |
| 465 | QVERIFY(waitForFinish(reply) == Success); |
| 466 | QCOMPARE(tokenReceived, expectedToken); |
| 467 | QCOMPARE(oauthHeaders["oauth_consumer_key" ], clientCredentials.first); |
| 468 | QCOMPARE(oauthHeaders["oauth_version" ], "1.0" ); |
| 469 | QString expectedSignature; |
| 470 | { |
| 471 | QVariantMap = oauthHeaders; |
| 472 | modifiedHeaders.insert(map: parameters); |
| 473 | modifiedHeaders.remove(key: "oauth_signature" ); |
| 474 | QOAuth1Signature signature(url, |
| 475 | clientCredentials.second, |
| 476 | token.second, |
| 477 | static_cast<QOAuth1Signature::HttpRequestMethod>(requestType), |
| 478 | modifiedHeaders); |
| 479 | switch (signatureMethod) { |
| 480 | case QOAuth1::SignatureMethod::PlainText: |
| 481 | expectedSignature = signature.plainText(); |
| 482 | break; |
| 483 | case QOAuth1::SignatureMethod::Hmac_Sha1: |
| 484 | expectedSignature = signature.hmacSha1().toBase64(); |
| 485 | break; |
| 486 | case QOAuth1::SignatureMethod::Rsa_Sha1: |
| 487 | expectedSignature = signature.rsaSha1(); |
| 488 | break; |
| 489 | } |
| 490 | } |
| 491 | QCOMPARE(oauthHeaders["oauth_signature" ], expectedSignature); |
| 492 | } |
| 493 | |
| 494 | void tst_OAuth1::prepareRequestSignature_data() |
| 495 | { |
| 496 | QTest::addColumn<QString>(name: "consumerKey" ); |
| 497 | QTest::addColumn<QString>(name: "consumerSecret" ); |
| 498 | QTest::addColumn<QString>(name: "accessKey" ); |
| 499 | QTest::addColumn<QString>(name: "accessKeySecret" ); |
| 500 | QTest::addColumn<QNetworkRequest>(name: "request" ); |
| 501 | QTest::addColumn<QByteArray>(name: "operation" ); |
| 502 | QTest::addColumn<QByteArray>(name: "body" ); |
| 503 | QTest::addColumn<QVariantMap>(name: "extraParams" ); |
| 504 | |
| 505 | QTest::newRow(dataTag: "get_simple" ) |
| 506 | << "key" |
| 507 | << "secret" |
| 508 | << "accesskey" |
| 509 | << "accesssecret" |
| 510 | << QNetworkRequest(QUrl("http://term.ie/oauth/example/echo_api.php" )) |
| 511 | << QByteArray("GET" ) |
| 512 | << QByteArray() |
| 513 | << QVariantMap(); |
| 514 | |
| 515 | QTest::newRow(dataTag: "get_params" ) |
| 516 | << "key" |
| 517 | << "secret" |
| 518 | << "accesskey" |
| 519 | << "accesssecret" |
| 520 | << QNetworkRequest(QUrl("http://term.ie/oauth/example/echo_api.php?" |
| 521 | "first=first&second=second" )) |
| 522 | << QByteArray("GET" ) |
| 523 | << QByteArray() |
| 524 | << QVariantMap(); |
| 525 | |
| 526 | QNetworkRequest postRequest(QUrl("http://term.ie/oauth/example/echo_api.php" )); |
| 527 | postRequest.setHeader(header: QNetworkRequest::ContentTypeHeader, |
| 528 | value: QByteArray("application/x-www-form-urlencoded" )); |
| 529 | QTest::newRow(dataTag: "post_params" ) |
| 530 | << "key" |
| 531 | << "secret" |
| 532 | << "accesskey" |
| 533 | << "accesssecret" |
| 534 | << postRequest |
| 535 | << QByteArray("POST" ) |
| 536 | << QByteArray("first=first&second=second" ) |
| 537 | << QVariantMap({ |
| 538 | {"first" , "first" }, |
| 539 | {"second" , "second" } |
| 540 | }); |
| 541 | |
| 542 | QTest::newRow(dataTag: "patch_param" ) |
| 543 | << "key" |
| 544 | << "secret" |
| 545 | << "accesskey" |
| 546 | << "accesssecret" |
| 547 | << QNetworkRequest(QUrl("http://term.ie/oauth/example/echo_api.php?" |
| 548 | "first=first&second=second" )) |
| 549 | << QByteArray("PATCH" ) |
| 550 | << QByteArray() |
| 551 | << QVariantMap(); |
| 552 | } |
| 553 | |
| 554 | void tst_OAuth1::prepareRequestSignature() |
| 555 | { |
| 556 | QFETCH(QString, consumerKey); |
| 557 | QFETCH(QString, consumerSecret); |
| 558 | QFETCH(QString, accessKey); |
| 559 | QFETCH(QString, accessKeySecret); |
| 560 | QFETCH(QNetworkRequest, request); |
| 561 | QFETCH(QByteArray, operation); |
| 562 | QFETCH(QByteArray, body); |
| 563 | QFETCH(QVariantMap, ); |
| 564 | |
| 565 | QOAuth1 o1; |
| 566 | o1.setClientCredentials(clientIdentifier: consumerKey, clientSharedSecret: consumerSecret); |
| 567 | o1.setTokenCredentials(token: accessKey, tokenSecret: accessKeySecret); |
| 568 | |
| 569 | o1.prepareRequest(request: &request, verb: operation, body); |
| 570 | |
| 571 | // extract oauth parameters from the headers |
| 572 | QVariantMap authArgs; |
| 573 | const auto = request.rawHeader(headerName: "Authorization" ); |
| 574 | QCOMPARE(authHeader.mid(0, 6), "OAuth " ); |
| 575 | const auto values = authHeader.mid(index: 6).split(sep: ','); |
| 576 | for (const auto &pair : values) { |
| 577 | const auto argPair = pair.split(sep: '='); |
| 578 | QCOMPARE(argPair.size(), 2); |
| 579 | QCOMPARE(argPair[1].front(), '\"'); |
| 580 | QCOMPARE(argPair[1].back(), '\"'); |
| 581 | authArgs.insert(key: argPair[0], value: argPair[1].mid(index: 1, len: argPair[1].size() - 2)); |
| 582 | } |
| 583 | |
| 584 | //compare known parameters |
| 585 | QCOMPARE(authArgs.value(oauthConsumerKey).toByteArray(), consumerKey); |
| 586 | QCOMPARE(authArgs.value(oauthToken).toByteArray(), accessKey); |
| 587 | QCOMPARE(authArgs.value(oauthSignatureMethod).toByteArray(), QByteArray("HMAC-SHA1" )); |
| 588 | QCOMPARE(authArgs.value(oauthVersion).toByteArray(), QByteArray("1.0" )); |
| 589 | QVERIFY(authArgs.contains(oauthNonce)); |
| 590 | QVERIFY(authArgs.contains(oauthTimestamp)); |
| 591 | QVERIFY(authArgs.contains(oauthSignature)); |
| 592 | |
| 593 | // verify the signature |
| 594 | const auto sigString = QUrl::fromPercentEncoding(authArgs.take(key: oauthSignature) |
| 595 | .toByteArray()).toUtf8(); |
| 596 | |
| 597 | authArgs.insert(map: extraParams); |
| 598 | QOAuth1Signature signature(request.url(), |
| 599 | consumerSecret, |
| 600 | accessKeySecret, |
| 601 | QOAuth1Signature::HttpRequestMethod::Custom, |
| 602 | authArgs); |
| 603 | signature.setCustomMethodString(operation); |
| 604 | const auto signatureData = signature.hmacSha1(); |
| 605 | QCOMPARE(signatureData.toBase64(), sigString); |
| 606 | } |
| 607 | |
| 608 | void tst_OAuth1::grant_data() |
| 609 | { |
| 610 | QTest::addColumn<QString>(name: "consumerKey" ); |
| 611 | QTest::addColumn<QString>(name: "consumerSecret" ); |
| 612 | QTest::addColumn<QString>(name: "requestToken" ); |
| 613 | QTest::addColumn<QString>(name: "requestTokenSecret" ); |
| 614 | QTest::addColumn<QString>(name: "accessToken" ); |
| 615 | QTest::addColumn<QString>(name: "accessTokenSecret" ); |
| 616 | QTest::addColumn<QUrl>(name: "requestTokenUrl" ); |
| 617 | QTest::addColumn<QUrl>(name: "accessTokenUrl" ); |
| 618 | QTest::addColumn<QUrl>(name: "authenticatedCallUrl" ); |
| 619 | QTest::addColumn<QNetworkAccessManager::Operation>(name: "requestType" ); |
| 620 | |
| 621 | if (hostReachable(host: QLatin1String("term.ie" ))) { |
| 622 | QTest::newRow(dataTag: "term.ie_get" ) << "key" |
| 623 | << "secret" |
| 624 | << "requestkey" |
| 625 | << "requestsecret" |
| 626 | << "accesskey" |
| 627 | << "accesssecret" |
| 628 | << QUrl("http://term.ie/oauth/example/request_token.php" ) |
| 629 | << QUrl("http://term.ie/oauth/example/access_token.php" ) |
| 630 | << QUrl("http://term.ie/oauth/example/echo_api.php" ) |
| 631 | << QNetworkAccessManager::GetOperation; |
| 632 | QTest::newRow(dataTag: "term.ie_post" ) << "key" |
| 633 | << "secret" |
| 634 | << "requestkey" |
| 635 | << "requestsecret" |
| 636 | << "accesskey" |
| 637 | << "accesssecret" |
| 638 | << QUrl("http://term.ie/oauth/example/request_token.php" ) |
| 639 | << QUrl("http://term.ie/oauth/example/access_token.php" ) |
| 640 | << QUrl("http://term.ie/oauth/example/echo_api.php" ) |
| 641 | << QNetworkAccessManager::PostOperation; |
| 642 | } else { |
| 643 | QSKIP("Skipping test due to unreachable term.ie host" ); |
| 644 | } |
| 645 | } |
| 646 | |
| 647 | void tst_OAuth1::grant() |
| 648 | { |
| 649 | QFETCH(QString, consumerKey); |
| 650 | QFETCH(QString, consumerSecret); |
| 651 | QFETCH(QString, requestToken); |
| 652 | QFETCH(QString, requestTokenSecret); |
| 653 | QFETCH(QString, accessToken); |
| 654 | QFETCH(QString, accessTokenSecret); |
| 655 | QFETCH(QUrl, requestTokenUrl); |
| 656 | QFETCH(QUrl, accessTokenUrl); |
| 657 | |
| 658 | bool tokenReceived = false; |
| 659 | QNetworkAccessManager networkAccessManager; |
| 660 | |
| 661 | QOAuth1 o1(&networkAccessManager); |
| 662 | |
| 663 | { |
| 664 | QSignalSpy clientIdentifierSpy(&o1, &QOAuth1::clientIdentifierChanged); |
| 665 | QSignalSpy clientSharedSecretSpy(&o1, &QOAuth1::clientSharedSecretChanged); |
| 666 | o1.setClientCredentials(clientIdentifier: consumerKey, clientSharedSecret: consumerSecret); |
| 667 | QCOMPARE(clientIdentifierSpy.count(), 1); |
| 668 | QCOMPARE(clientSharedSecretSpy.count(), 1); |
| 669 | } |
| 670 | { |
| 671 | QSignalSpy spy(&o1, &QOAuth1::temporaryCredentialsUrlChanged); |
| 672 | o1.setTemporaryCredentialsUrl(requestTokenUrl); |
| 673 | QCOMPARE(spy.count(), 1); |
| 674 | } |
| 675 | { |
| 676 | QSignalSpy spy(&o1, &QOAuth1::tokenCredentialsUrlChanged); |
| 677 | o1.setTokenCredentialsUrl(accessTokenUrl); |
| 678 | QCOMPARE(spy.count(), 1); |
| 679 | } |
| 680 | connect(sender: &o1, signal: &QAbstractOAuth::statusChanged, slot: [&](QAbstractOAuth::Status status) { |
| 681 | if (status == QAbstractOAuth::Status::TemporaryCredentialsReceived) { |
| 682 | if (!requestToken.isEmpty()) |
| 683 | QCOMPARE(requestToken, o1.tokenCredentials().first); |
| 684 | if (!requestTokenSecret.isEmpty()) |
| 685 | QCOMPARE(requestTokenSecret, o1.tokenCredentials().second); |
| 686 | tokenReceived = true; |
| 687 | } else if (status == QAbstractOAuth::Status::Granted) { |
| 688 | if (!accessToken.isEmpty()) |
| 689 | QCOMPARE(accessToken, o1.tokenCredentials().first); |
| 690 | if (!accessTokenSecret.isEmpty()) |
| 691 | QCOMPARE(accessTokenSecret, o1.tokenCredentials().second); |
| 692 | tokenReceived = true; |
| 693 | } |
| 694 | }); |
| 695 | |
| 696 | QEventLoop eventLoop; |
| 697 | |
| 698 | QSignalSpy grantSignalSpy(&o1, &QOAuth1::granted); |
| 699 | QTimer::singleShot(interval: 10000, receiver: &eventLoop, slot: &QEventLoop::quit); |
| 700 | connect(sender: &o1, signal: &QOAuth1::granted, receiver: &eventLoop, slot: &QEventLoop::quit); |
| 701 | o1.grant(); |
| 702 | eventLoop.exec(); |
| 703 | QVERIFY(tokenReceived); |
| 704 | QCOMPARE(grantSignalSpy.count(), 1); |
| 705 | QCOMPARE(o1.status(), QAbstractOAuth::Status::Granted); |
| 706 | } |
| 707 | |
| 708 | void tst_OAuth1::authenticatedCalls_data() |
| 709 | { |
| 710 | QTest::addColumn<QString>(name: "consumerKey" ); |
| 711 | QTest::addColumn<QString>(name: "consumerSecret" ); |
| 712 | QTest::addColumn<QString>(name: "accessKey" ); |
| 713 | QTest::addColumn<QString>(name: "accessKeySecret" ); |
| 714 | QTest::addColumn<QUrl>(name: "url" ); |
| 715 | QTest::addColumn<QVariantMap>(name: "parameters" ); |
| 716 | QTest::addColumn<QNetworkAccessManager::Operation>(name: "operation" ); |
| 717 | |
| 718 | const QVariantMap parameters { { QStringLiteral("first" ), QStringLiteral("first" ) }, |
| 719 | { QStringLiteral("second" ), QStringLiteral("second" ) }, |
| 720 | { QStringLiteral("third" ), QStringLiteral("third" ) }, |
| 721 | { QStringLiteral("c2&a3" ), QStringLiteral("2=%$&@q" ) } |
| 722 | }; |
| 723 | |
| 724 | if (hostReachable(host: QLatin1String("term.ie" ))) { |
| 725 | QTest::newRow(dataTag: "term.ie_get" ) << "key" |
| 726 | << "secret" |
| 727 | << "accesskey" |
| 728 | << "accesssecret" |
| 729 | << QUrl("http://term.ie/oauth/example/echo_api.php" ) |
| 730 | << parameters |
| 731 | << QNetworkAccessManager::GetOperation; |
| 732 | QTest::newRow(dataTag: "term.ie_post" ) << "key" |
| 733 | << "secret" |
| 734 | << "accesskey" |
| 735 | << "accesssecret" |
| 736 | << QUrl("http://term.ie/oauth/example/echo_api.php" ) |
| 737 | << parameters |
| 738 | << QNetworkAccessManager::PostOperation; |
| 739 | QTest::newRow(dataTag: "term.ie_percent_encoded_query" ) |
| 740 | << "key" |
| 741 | << "secret" |
| 742 | << "accesskey" |
| 743 | << "accesssecret" |
| 744 | << QUrl("http://term.ie/oauth/example/echo_api.php?key=%40value+1%2B2=3" ) |
| 745 | << parameters |
| 746 | << QNetworkAccessManager::GetOperation; |
| 747 | } else { |
| 748 | QSKIP("Skipping test due to unreachable term.ie host" ); |
| 749 | } |
| 750 | } |
| 751 | |
| 752 | void tst_OAuth1::authenticatedCalls() |
| 753 | { |
| 754 | QFETCH(QString, consumerKey); |
| 755 | QFETCH(QString, consumerSecret); |
| 756 | QFETCH(QString, accessKey); |
| 757 | QFETCH(QString, accessKeySecret); |
| 758 | QFETCH(QUrl, url); |
| 759 | QFETCH(QVariantMap, parameters); |
| 760 | QFETCH(QNetworkAccessManager::Operation, operation); |
| 761 | |
| 762 | QNetworkAccessManager networkAccessManager; |
| 763 | QNetworkReplyPtr reply; |
| 764 | QString receivedData; |
| 765 | QString ; |
| 766 | { |
| 767 | if (url.hasQuery()) { |
| 768 | parametersString = url.query(QUrl::FullyDecoded); |
| 769 | if (!parameters.empty()) |
| 770 | parametersString.append(c: QLatin1Char('&')); |
| 771 | } |
| 772 | bool first = true; |
| 773 | for (auto it = parameters.begin(), end = parameters.end(); it != end; ++it) { |
| 774 | if (first) |
| 775 | first = false; |
| 776 | else |
| 777 | parametersString += QLatin1Char('&'); |
| 778 | parametersString += it.key() + QLatin1Char('=') + it.value().toString(); |
| 779 | } |
| 780 | } |
| 781 | |
| 782 | QOAuth1 o1(&networkAccessManager); |
| 783 | o1.setClientCredentials(clientIdentifier: consumerKey, clientSharedSecret: consumerSecret); |
| 784 | o1.setTokenCredentials(token: accessKey, tokenSecret: accessKeySecret); |
| 785 | if (operation == QNetworkAccessManager::GetOperation) |
| 786 | reply.reset(t: o1.get(url, parameters)); |
| 787 | else if (operation == QNetworkAccessManager::PostOperation) |
| 788 | reply.reset(t: o1.post(url, parameters)); |
| 789 | QVERIFY(!reply.isNull()); |
| 790 | QVERIFY(!reply->isFinished()); |
| 791 | |
| 792 | connect(sender: &networkAccessManager, signal: &QNetworkAccessManager::finished, |
| 793 | slot: [&](QNetworkReply *reply) { |
| 794 | QByteArray data = reply->readAll(); |
| 795 | QUrlQuery query(QString::fromUtf8(str: data)); |
| 796 | receivedData = query.toString(encoding: QUrl::FullyDecoded); |
| 797 | }); |
| 798 | QVERIFY(waitForFinish(reply) == Success); |
| 799 | QCOMPARE(receivedData, parametersString); |
| 800 | reply.clear(); |
| 801 | } |
| 802 | |
| 803 | void tst_OAuth1::prepareRequestCalls_data() |
| 804 | { |
| 805 | QTest::addColumn<QString>(name: "consumerKey" ); |
| 806 | QTest::addColumn<QString>(name: "consumerSecret" ); |
| 807 | QTest::addColumn<QString>(name: "accessKey" ); |
| 808 | QTest::addColumn<QString>(name: "accessKeySecret" ); |
| 809 | QTest::addColumn<QUrl>(name: "url" ); |
| 810 | QTest::addColumn<QVariantMap>(name: "parameters" ); |
| 811 | QTest::addColumn<QByteArray>(name: "operation" ); |
| 812 | |
| 813 | const QVariantMap parameters { { QStringLiteral("first" ), QStringLiteral("first" ) }, |
| 814 | { QStringLiteral("second" ), QStringLiteral("second" ) }, |
| 815 | { QStringLiteral("third" ), QStringLiteral("third" ) }, |
| 816 | { QStringLiteral("c2&a3" ), QStringLiteral("2=%$&@q" ) } |
| 817 | }; |
| 818 | |
| 819 | if (hostReachable(host: QLatin1String("term.ie" ))) { |
| 820 | QTest::newRow(dataTag: "term.ie_get" ) << "key" |
| 821 | << "secret" |
| 822 | << "accesskey" |
| 823 | << "accesssecret" |
| 824 | << QUrl("http://term.ie/oauth/example/echo_api.php" ) |
| 825 | << parameters |
| 826 | << QByteArray("GET" ); |
| 827 | QTest::newRow(dataTag: "term.ie_post" ) << "key" |
| 828 | << "secret" |
| 829 | << "accesskey" |
| 830 | << "accesssecret" |
| 831 | << QUrl("http://term.ie/oauth/example/echo_api.php" ) |
| 832 | << parameters |
| 833 | << QByteArray("POST" ); |
| 834 | QTest::newRow(dataTag: "term.ie_percent_encoded_query" ) |
| 835 | << "key" |
| 836 | << "secret" |
| 837 | << "accesskey" |
| 838 | << "accesssecret" |
| 839 | << QUrl("http://term.ie/oauth/example/echo_api.php?key=%40value+1%2B2=3" ) |
| 840 | << parameters |
| 841 | << QByteArray("GET" ); |
| 842 | } else { |
| 843 | QSKIP("Skipping test due to unreachable term.ie host" ); |
| 844 | } |
| 845 | } |
| 846 | |
| 847 | void tst_OAuth1::prepareRequestCalls() |
| 848 | { |
| 849 | QFETCH(QString, consumerKey); |
| 850 | QFETCH(QString, consumerSecret); |
| 851 | QFETCH(QString, accessKey); |
| 852 | QFETCH(QString, accessKeySecret); |
| 853 | QFETCH(QUrl, url); |
| 854 | QFETCH(QVariantMap, parameters); |
| 855 | QFETCH(QByteArray, operation); |
| 856 | |
| 857 | QNetworkAccessManager networkAccessManager; |
| 858 | QNetworkReplyPtr reply; |
| 859 | QString receivedData; |
| 860 | QString ; |
| 861 | { |
| 862 | if (url.hasQuery()) { |
| 863 | parametersString = url.query(QUrl::FullyDecoded); |
| 864 | if (!parameters.empty()) |
| 865 | parametersString.append(c: QLatin1Char('&')); |
| 866 | } |
| 867 | bool first = true; |
| 868 | for (auto it = parameters.begin(), end = parameters.end(); it != end; ++it) { |
| 869 | if (first) |
| 870 | first = false; |
| 871 | else |
| 872 | parametersString += QLatin1Char('&'); |
| 873 | parametersString += it.key() + QLatin1Char('=') + it.value().toString(); |
| 874 | } |
| 875 | } |
| 876 | |
| 877 | QOAuth1 o1(&networkAccessManager); |
| 878 | o1.setClientCredentials(clientIdentifier: consumerKey, clientSharedSecret: consumerSecret); |
| 879 | o1.setTokenCredentials(token: accessKey, tokenSecret: accessKeySecret); |
| 880 | |
| 881 | if (operation != "POST" ) { |
| 882 | QUrlQuery query(url.query()); |
| 883 | for (auto it = parameters.begin(), end = parameters.end(); it != end; ++it) |
| 884 | query.addQueryItem(key: it.key(), value: it.value().toString()); |
| 885 | url.setQuery(query); |
| 886 | } |
| 887 | QNetworkRequest request(url); |
| 888 | QByteArray body; |
| 889 | if (operation == "POST" ) { |
| 890 | QUrlQuery query(url.query()); |
| 891 | for (auto it = parameters.begin(), end = parameters.end(); it != end; ++it) |
| 892 | query.addQueryItem(key: it.key(), value: it.value().toString()); |
| 893 | body = query.toString().toUtf8(); |
| 894 | request.setHeader(header: QNetworkRequest::ContentTypeHeader, |
| 895 | value: QByteArray("application/x-www-form-urlencoded" )); |
| 896 | } |
| 897 | |
| 898 | o1.prepareRequest(request: &request, verb: operation, body); |
| 899 | if (body.isEmpty()) |
| 900 | reply.reset(t: o1.networkAccessManager()->sendCustomRequest(request, verb: operation)); |
| 901 | else |
| 902 | reply.reset(t: o1.networkAccessManager()->sendCustomRequest(request, verb: operation, data: body)); |
| 903 | QVERIFY(!reply.isNull()); |
| 904 | QVERIFY(!reply->isFinished()); |
| 905 | |
| 906 | connect(sender: &networkAccessManager, signal: &QNetworkAccessManager::finished, |
| 907 | slot: [&](QNetworkReply *reply) { |
| 908 | QByteArray data = reply->readAll(); |
| 909 | QUrlQuery query(QString::fromUtf8(str: data)); |
| 910 | receivedData = query.toString(encoding: QUrl::FullyDecoded); |
| 911 | }); |
| 912 | QVERIFY(waitForFinish(reply) == Success); |
| 913 | QCOMPARE(receivedData, parametersString); |
| 914 | reply.clear(); |
| 915 | } |
| 916 | |
| 917 | void tst_OAuth1::secondTemporaryToken() |
| 918 | { |
| 919 | QNetworkAccessManager networkAccessManager; |
| 920 | |
| 921 | const StringPair expectedToken(qMakePair(QStringLiteral("temporaryKey" ), QStringLiteral("temporaryToken" ))); |
| 922 | WebServer webServer([&](const WebServer::HttpRequest &request, QTcpSocket *socket) { |
| 923 | Q_UNUSED(request); |
| 924 | const QString format = "oauth_token=%1&oauth_token_secret=%2&oauth_callback_confirmed=true" ; |
| 925 | const QByteArray text = format.arg(a1: expectedToken.first, a2: expectedToken.second).toUtf8(); |
| 926 | const QByteArray replyMessage { |
| 927 | "HTTP/1.0 200 OK\r\n" |
| 928 | "Content-Type: application/x-www-form-urlencoded; charset=\"utf-8\"\r\n" |
| 929 | "Content-Length: " + QByteArray::number(text.size()) + "\r\n\r\n" |
| 930 | + text |
| 931 | }; |
| 932 | socket->write(data: replyMessage); |
| 933 | }); |
| 934 | |
| 935 | QOAuth1 o1(&networkAccessManager); |
| 936 | |
| 937 | StringPair clientCredentials = qMakePair(QStringLiteral("user" ), QStringLiteral("passwd" )); |
| 938 | o1.setClientCredentials(clientCredentials); |
| 939 | o1.setTemporaryCredentialsUrl(webServer.url(QStringLiteral("temporary" ))); |
| 940 | o1.setAuthorizationUrl(webServer.url(QStringLiteral("authorization" ))); |
| 941 | o1.setTokenCredentialsUrl(webServer.url(QStringLiteral("token" ))); |
| 942 | |
| 943 | StringPair tokenReceived; |
| 944 | connect(sender: &o1, signal: &QOAuth1::tokenChanged, slot: [&tokenReceived](const QString &token) { |
| 945 | tokenReceived.first = token; |
| 946 | }); |
| 947 | bool replyReceived = false; |
| 948 | connect(sender: &o1, signal: &QOAuth1::tokenSecretChanged, slot: [&tokenReceived, &replyReceived](const QString &tokenSecret) { |
| 949 | tokenReceived.second = tokenSecret; |
| 950 | replyReceived = true; |
| 951 | }); |
| 952 | |
| 953 | o1.grant(); |
| 954 | QTRY_VERIFY(replyReceived); |
| 955 | |
| 956 | QVERIFY(!tokenReceived.first.isEmpty()); |
| 957 | QVERIFY(!tokenReceived.second.isEmpty()); |
| 958 | QCOMPARE(o1.status(), QAbstractOAuth::Status::TemporaryCredentialsReceived); |
| 959 | QCOMPARE(tokenReceived, expectedToken); |
| 960 | |
| 961 | replyReceived = false; // reset this so we can 'synchronize' on it again |
| 962 | // Do the same request again, should end up in the same state!! |
| 963 | o1.grant(); |
| 964 | QTRY_VERIFY(replyReceived); |
| 965 | |
| 966 | QVERIFY(!tokenReceived.first.isEmpty()); |
| 967 | QVERIFY(!tokenReceived.second.isEmpty()); |
| 968 | QCOMPARE(o1.status(), QAbstractOAuth::Status::TemporaryCredentialsReceived); |
| 969 | QCOMPARE(tokenReceived, expectedToken); |
| 970 | } |
| 971 | |
| 972 | QTEST_MAIN(tst_OAuth1) |
| 973 | #include "tst_oauth1.moc" |
| 974 | |