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
41Q_DECLARE_METATYPE(QNetworkAccessManager::Operation)
42Q_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
48typedef QSharedPointer<QNetworkReply> QNetworkReplyPtr;
49
50class 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
77public:
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
141public Q_SLOTS:
142 void finished();
143 void gotError();
144
145private 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
173const auto oauthVersion = QStringLiteral("oauth_version");
174const auto oauthConsumerKey = QStringLiteral("oauth_consumer_key");
175const auto oauthNonce = QStringLiteral("oauth_nonce");
176const auto oauthSignatureMethod = QStringLiteral("oauth_signature_method");
177const auto oauthTimestamp = QStringLiteral("oauth_timestamp");
178const auto oauthToken = QStringLiteral("oauth_token");
179const auto oauthSignature = QStringLiteral("oauth_signature");
180
181bool 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
197int 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
220void 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
227void tst_OAuth1::finished()
228{
229 if (loop)
230 loop->exit(returnCode: returnCode = Success);
231}
232
233void 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
240void 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
256void 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
272void 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
288void 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
304void 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
316void 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
328void 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
340void 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
353void 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
419void 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 oauthHeaders;
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 modifiedHeaders = 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
494void 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
554void 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, extraParams);
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 authHeader = 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
608void 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
647void 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
708void 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
752void 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 parametersString;
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
803void 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
847void 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 parametersString;
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
917void 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
972QTEST_MAIN(tst_OAuth1)
973#include "tst_oauth1.moc"
974

source code of qtnetworkauth/tests/auto/oauth1/tst_oauth1.cpp