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 Qt Network Auth module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL$
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 or (at your option) any later version
20** approved by the KDE Free Qt Foundation. The licenses are as published by
21** the Free Software Foundation and appearing in the file LICENSE.GPL3
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29
30#include <QtNetwork/qtnetwork-config.h>
31
32#ifndef QT_NO_HTTP
33
34#include "qoauth1.h"
35#include "qoauth1_p.h"
36#include "qoauth1signature.h"
37#include "qoauthoobreplyhandler.h"
38#include "qoauthhttpserverreplyhandler.h"
39
40#include <QtCore/qmap.h>
41#include <QtCore/qlist.h>
42#include <QtCore/qvariant.h>
43#include <QtCore/qurlquery.h>
44#include <QtCore/qdatetime.h>
45#include <QtCore/qbytearray.h>
46#include <QtCore/qmessageauthenticationcode.h>
47
48#include <QtNetwork/qnetworkreply.h>
49#include <QtNetwork/qnetworkrequest.h>
50#include <QtNetwork/qnetworkaccessmanager.h>
51
52QT_BEGIN_NAMESPACE
53
54/*!
55 \class QOAuth1
56 \inmodule QtNetworkAuth
57 \ingroup oauth
58 \brief The QOAuth1 class provides an implementation of the
59 \l {https://tools.ietf.org/html/rfc5849}{OAuth 1 Protocol}.
60 \since 5.8
61
62 QOAuth1 provides a method for clients to access server resources
63 on behalf of a resource owner (such as a different client or an
64 end-user). It also provides a process for end-users to authorize
65 third-party access to their server resources without sharing
66 their credentials (typically, a username and password pair),
67 using user-agent redirections.
68
69 QOAuth1 uses tokens to represent the authorization granted to the
70 client by the resource owner. Typically, token credentials are
71 issued by the server at the resource owner's request, after
72 authenticating the resource owner's identity (usually using a
73 username and password).
74
75 When making the temporary credentials request, the client
76 authenticates using only the client credentials. When making the
77 token request, the client authenticates using the client
78 credentials as well as the temporary credentials. Once the
79 client receives and stores the token credentials, it can
80 proceed to access protected resources on behalf of the resource
81 owner by making authenticated requests using the client
82 credentials together with the token credentials received.
83*/
84
85/*!
86 \enum QOAuth1::SignatureMethod
87
88 Indicates the signature method to be used to sign requests.
89
90 \value Hmac_Sha1
91 \l {https://tools.ietf.org/html/rfc5849#section-3.4.2}
92 {HMAC-SHA1} signature method.
93
94 \value Rsa_Sha1
95 \l {https://tools.ietf.org/html/rfc5849#section-3.4.3}
96 {RSA-SHA1} signature method (not supported).
97
98 \value PlainText
99 \l {https://tools.ietf.org/html/rfc5849#section-3.4.4}
100 {PLAINTEXT} signature method.
101*/
102
103using Key = QOAuth1Private::OAuth1KeyString;
104const QString Key::oauthCallback = QStringLiteral("oauth_callback");
105const QString Key::oauthCallbackConfirmed = QStringLiteral("oauth_callback_confirmed");
106const QString Key::oauthConsumerKey = QStringLiteral("oauth_consumer_key");
107const QString Key::oauthNonce = QStringLiteral("oauth_nonce");
108const QString Key::oauthSignature = QStringLiteral("oauth_signature");
109const QString Key::oauthSignatureMethod = QStringLiteral("oauth_signature_method");
110const QString Key::oauthTimestamp = QStringLiteral("oauth_timestamp");
111const QString Key::oauthToken = QStringLiteral("oauth_token");
112const QString Key::oauthTokenSecret = QStringLiteral("oauth_token_secret");
113const QString Key::oauthVerifier = QStringLiteral("oauth_verifier");
114const QString Key::oauthVersion = QStringLiteral("oauth_version");
115
116QOAuth1Private::QOAuth1Private(const QPair<QString, QString> &clientCredentials,
117 QNetworkAccessManager *networkAccessManager) :
118 QAbstractOAuthPrivate("qt.networkauth.oauth1",
119 QUrl(),
120 clientCredentials.first,
121 networkAccessManager),
122 clientIdentifierSharedKey(clientCredentials.second)
123{
124 qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
125 qRegisterMetaType<QOAuth1::SignatureMethod>("QOAuth1::SignatureMethod");
126}
127
128void QOAuth1Private::appendCommonHeaders(QVariantMap *headers)
129{
130 const auto currentDateTime = QDateTime::currentDateTimeUtc();
131
132 headers->insert(Key::oauthNonce, QOAuth1::nonce());
133 headers->insert(Key::oauthConsumerKey, clientIdentifier);
134 headers->insert(Key::oauthTimestamp, QString::number(currentDateTime.toSecsSinceEpoch()));
135 headers->insert(Key::oauthVersion, oauthVersion);
136 headers->insert(Key::oauthSignatureMethod, signatureMethodString().toUtf8());
137}
138
139void QOAuth1Private::appendSignature(QAbstractOAuth::Stage stage,
140 QVariantMap *headers,
141 const QUrl &url,
142 QNetworkAccessManager::Operation operation,
143 const QVariantMap parameters)
144{
145 QByteArray signature;
146 {
147 QMultiMap<QString, QVariant> headerCopy = *headers;
148 QVariantMap allParameters = headerCopy.unite(parameters);
149 if (modifyParametersFunction)
150 modifyParametersFunction(stage, &allParameters);
151 signature = generateSignature(allParameters, url, operation);
152 }
153 headers->insert(Key::oauthSignature, signature);
154}
155
156QNetworkReply *QOAuth1Private::requestToken(QNetworkAccessManager::Operation operation,
157 const QUrl &url,
158 const QPair<QString, QString> &token,
159 const QVariantMap &parameters)
160{
161 if (Q_UNLIKELY(!networkAccessManager())) {
162 qCWarning(loggingCategory, "QNetworkAccessManager not available");
163 return nullptr;
164 }
165 if (Q_UNLIKELY(url.isEmpty())) {
166 qCWarning(loggingCategory, "Request Url not set");
167 return nullptr;
168 }
169 if (Q_UNLIKELY(operation != QNetworkAccessManager::GetOperation &&
170 operation != QNetworkAccessManager::PostOperation)) {
171 qCWarning(loggingCategory, "Operation not supported");
172 return nullptr;
173 }
174
175 QNetworkRequest request(url);
176 request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
177
178 QAbstractOAuth::Stage stage = QAbstractOAuth::Stage::RequestingTemporaryCredentials;
179 QVariantMap headers;
180 QVariantMap remainingParameters;
181 appendCommonHeaders(&headers);
182 for (auto it = parameters.begin(), end = parameters.end(); it != end; ++it) {
183 const auto key = it.key();
184 const auto value = it.value();
185 if (key.startsWith(QStringLiteral("oauth_")))
186 headers.insert(key, value);
187 else
188 remainingParameters.insert(key, value);
189 }
190 if (!token.first.isEmpty()) {
191 headers.insert(Key::oauthToken, token.first);
192 stage = QAbstractOAuth::Stage::RequestingAccessToken;
193 }
194 appendSignature(stage, &headers, url, operation, remainingParameters);
195
196 request.setRawHeader("Authorization", QOAuth1::generateAuthorizationHeader(headers));
197
198 QNetworkReply *reply = nullptr;
199 if (operation == QNetworkAccessManager::GetOperation) {
200 if (parameters.size() > 0) {
201 QUrl url = request.url();
202 url.setQuery(QOAuth1Private::createQuery(remainingParameters));
203 request.setUrl(url);
204 }
205 reply = networkAccessManager()->get(request);
206 }
207 else if (operation == QNetworkAccessManager::PostOperation) {
208 QUrlQuery query = QOAuth1Private::createQuery(remainingParameters);
209 const QByteArray data = query.toString(QUrl::FullyEncoded).toUtf8();
210 request.setHeader(QNetworkRequest::ContentTypeHeader,
211 QStringLiteral("application/x-www-form-urlencoded"));
212 reply = networkAccessManager()->post(request, data);
213 }
214
215 connect(reply, &QNetworkReply::errorOccurred,
216 this, &QOAuth1Private::_q_onTokenRequestError);
217
218 QAbstractOAuthReplyHandler *handler = replyHandler ? replyHandler.data()
219 : defaultReplyHandler.data();
220 QObject::connect(reply, &QNetworkReply::finished,
221 [handler, reply]() { handler->networkReplyFinished(reply); });
222 connect(handler, &QAbstractOAuthReplyHandler::tokensReceived, this,
223 &QOAuth1Private::_q_tokensReceived);
224
225 return reply;
226}
227
228QString QOAuth1Private::signatureMethodString() const
229{
230 switch (signatureMethod) { // No default: intended
231 case QOAuth1::SignatureMethod::PlainText:
232 return QStringLiteral("PLAINTEXT");
233 case QOAuth1::SignatureMethod::Hmac_Sha1:
234 return QStringLiteral("HMAC-SHA1");
235 case QOAuth1::SignatureMethod::Rsa_Sha1:
236 qFatal("RSA-SHA1 signature method not supported");
237 return QStringLiteral("RSA-SHA1");
238 }
239 qFatal("Invalid signature method");
240 return QString();
241}
242
243QByteArray QOAuth1Private::generateSignature(const QVariantMap &parameters,
244 const QUrl &url,
245 QNetworkAccessManager::Operation operation) const
246{
247 QOAuth1Signature signature(url,
248 clientIdentifierSharedKey,
249 tokenSecret,
250 static_cast<QOAuth1Signature::HttpRequestMethod>(operation),
251 parameters);
252 return formatSignature(signature);
253}
254
255QByteArray QOAuth1Private::generateSignature(const QVariantMap &parameters,
256 const QUrl &url,
257 const QByteArray &verb) const
258{
259 QOAuth1Signature signature(url,
260 clientIdentifierSharedKey,
261 tokenSecret,
262 QOAuth1Signature::HttpRequestMethod::Custom,
263 parameters);
264 signature.setCustomMethodString(verb);
265 return formatSignature(signature);
266}
267
268QByteArray QOAuth1Private::formatSignature(const QOAuth1Signature &signature) const
269{
270 switch (signatureMethod) {
271 case QOAuth1::SignatureMethod::Hmac_Sha1:
272 return signature.hmacSha1().toBase64();
273 case QOAuth1::SignatureMethod::PlainText:
274 return signature.plainText();
275 default:
276 qFatal("QOAuth1Private::generateSignature: Signature method not supported");
277 return QByteArray();
278 }
279}
280
281QVariantMap QOAuth1Private::createOAuthBaseParams() const
282{
283 QVariantMap oauthParams;
284
285 const auto currentDateTime = QDateTime::currentDateTimeUtc();
286
287 oauthParams.insert(Key::oauthConsumerKey, clientIdentifier);
288 oauthParams.insert(Key::oauthVersion, QStringLiteral("1.0"));
289 oauthParams.insert(Key::oauthToken, token);
290 oauthParams.insert(Key::oauthSignatureMethod, signatureMethodString());
291 oauthParams.insert(Key::oauthNonce, QOAuth1::nonce());
292 oauthParams.insert(Key::oauthTimestamp, QString::number(currentDateTime.toSecsSinceEpoch()));
293
294 return oauthParams;
295}
296
297void QOAuth1Private::prepareRequestImpl(QNetworkRequest *request,
298 const QByteArray &verb,
299 const QByteArray &body)
300{
301 Q_Q(QOAuth1);
302 QVariantMap signingParams;
303 if (verb == "POST" &&
304 request->header(QNetworkRequest::ContentTypeHeader).toByteArray()
305 == "application/x-www-form-urlencoded") {
306 QUrlQuery query(QString::fromUtf8(body));
307 for (const auto &item : query.queryItems(QUrl::FullyDecoded))
308 signingParams.insert(item.first, item.second);
309 }
310 q->setup(request, signingParams, verb);
311}
312
313void QOAuth1Private::_q_onTokenRequestError(QNetworkReply::NetworkError error)
314{
315 Q_Q(QOAuth1);
316 Q_UNUSED(error);
317 Q_EMIT q->requestFailed(QAbstractOAuth::Error::NetworkError);
318}
319
320void QOAuth1Private::_q_tokensReceived(const QVariantMap &tokens)
321{
322 Q_Q(QOAuth1);
323
324 if (!tokenRequested && status == QAbstractOAuth::Status::TemporaryCredentialsReceived) {
325 // We didn't request a token yet, but in the "TemporaryCredentialsReceived" state _any_
326 // new tokens received will count as a successful authentication and we move to the
327 // 'Granted' state. To avoid this, 'status' will be temporarily set to 'NotAuthenticated'.
328 status = QAbstractOAuth::Status::NotAuthenticated;
329 }
330 if (tokenRequested) // 'Reset' tokenRequested now that we've gotten new tokens
331 tokenRequested = false;
332
333 QPair<QString, QString> credential(tokens.value(Key::oauthToken).toString(),
334 tokens.value(Key::oauthTokenSecret).toString());
335 switch (status) {
336 case QAbstractOAuth::Status::NotAuthenticated:
337 if (tokens.value(Key::oauthCallbackConfirmed, true).toBool()) {
338 q->setTokenCredentials(credential);
339 setStatus(QAbstractOAuth::Status::TemporaryCredentialsReceived);
340 } else {
341 Q_EMIT q->requestFailed(QAbstractOAuth::Error::OAuthCallbackNotVerified);
342 }
343 break;
344 case QAbstractOAuth::Status::TemporaryCredentialsReceived:
345 q->setTokenCredentials(credential);
346 setStatus(QAbstractOAuth::Status::Granted);
347 break;
348 case QAbstractOAuth::Status::Granted:
349 case QAbstractOAuth::Status::RefreshingToken:
350 break;
351 }
352}
353
354/*!
355 Constructs a QOAuth1 object with parent object \a parent.
356*/
357QOAuth1::QOAuth1(QObject *parent) :
358 QOAuth1(nullptr,
359 parent)
360{}
361
362/*!
363 Constructs a QOAuth1 object with parent object \a parent, using
364 \a manager to access the network.
365*/
366QOAuth1::QOAuth1(QNetworkAccessManager *manager, QObject *parent) :
367 QOAuth1(QString(),
368 QString(),
369 manager,
370 parent)
371{}
372
373/*!
374 Constructs a QOAuth1 object with parent object \a parent, using
375 \a manager to access the network.
376 Also sets \a clientIdentifier and \a clientSharedSecret to sign
377 the calls to the web server and identify the application.
378*/
379QOAuth1::QOAuth1(const QString &clientIdentifier,
380 const QString &clientSharedSecret,
381 QNetworkAccessManager *manager,
382 QObject *parent)
383 : QAbstractOAuth(*new QOAuth1Private(qMakePair(clientIdentifier, clientSharedSecret),
384 manager),
385 parent)
386{}
387
388/*!
389 Returns the current shared secret used to sign requests to
390 the web server.
391
392 \sa setClientSharedSecret(), clientCredentials()
393*/
394QString QOAuth1::clientSharedSecret() const
395{
396 Q_D(const QOAuth1);
397 return d->clientIdentifierSharedKey;
398}
399
400/*!
401 Sets \a clientSharedSecret as the string used to sign the
402 requests to the web server.
403
404 \sa clientSharedSecret(), setClientCredentials()
405*/
406void QOAuth1::setClientSharedSecret(const QString &clientSharedSecret)
407{
408 Q_D(QOAuth1);
409 if (d->clientIdentifierSharedKey != clientSharedSecret) {
410 d->clientIdentifierSharedKey = clientSharedSecret;
411 Q_EMIT clientSharedSecretChanged(clientSharedSecret);
412 }
413}
414
415/*!
416 Returns the pair of QString used to identify the application and
417 sign requests to the web server.
418
419 \sa setClientCredentials()
420*/
421QPair<QString, QString> QOAuth1::clientCredentials() const
422{
423 Q_D(const QOAuth1);
424 return qMakePair(d->clientIdentifier, d->clientIdentifierSharedKey);
425}
426
427/*!
428 Sets \a clientCredentials as the pair of QString used to identify
429 the application and sign requests to the web server.
430
431 \sa clientCredentials()
432*/
433void QOAuth1::setClientCredentials(const QPair<QString, QString> &clientCredentials)
434{
435 setClientCredentials(clientCredentials.first, clientCredentials.second);
436}
437
438/*!
439 Sets \a clientIdentifier and \a clientSharedSecret as the pair of
440 QString used to identify the application and sign requests to the
441 web server. \a clientIdentifier identifies the application and
442 \a clientSharedSecret is used to sign requests.
443
444 \sa clientCredentials()
445*/
446void QOAuth1::setClientCredentials(const QString &clientIdentifier,
447 const QString &clientSharedSecret)
448{
449 setClientIdentifier(clientIdentifier);
450 setClientSharedSecret(clientSharedSecret);
451}
452
453/*!
454 Returns the current token secret used to sign authenticated
455 requests to the web server.
456
457 \sa setTokenSecret(), tokenCredentials()
458*/
459QString QOAuth1::tokenSecret() const
460{
461 Q_D(const QOAuth1);
462 return d->tokenSecret;
463}
464/*!
465 Sets \a tokenSecret as the current token secret used to sign
466 authenticated calls to the web server.
467
468 \sa tokenSecret(), setTokenCredentials()
469*/
470void QOAuth1::setTokenSecret(const QString &tokenSecret)
471{
472 Q_D(QOAuth1);
473 if (d->tokenSecret != tokenSecret) {
474 d->tokenSecret = tokenSecret;
475 Q_EMIT tokenSecretChanged(tokenSecret);
476 }
477}
478
479/*!
480 Returns the pair of QString used to identify and sign
481 authenticated requests to the web server.
482
483 \sa setTokenCredentials()
484*/
485QPair<QString, QString> QOAuth1::tokenCredentials() const
486{
487 Q_D(const QOAuth1);
488 return qMakePair(d->token, d->tokenSecret);
489}
490
491/*!
492 Sets \a tokenCredentials as the pair of QString used to identify
493 and sign authenticated requests to the web server.
494
495 \sa tokenCredentials()
496*/
497void QOAuth1::setTokenCredentials(const QPair<QString, QString> &tokenCredentials)
498{
499 setTokenCredentials(tokenCredentials.first, tokenCredentials.second);
500}
501
502/*!
503 Sets \a token and \a tokenSecret as the pair of QString used to
504 identify and sign authenticated requests to the web server.
505 Once the client receives and stores the token credentials, it can
506 proceed to access protected resources on behalf of the resource
507 owner by making authenticated requests using the client
508 credentials together with the token credentials received.
509
510 \sa tokenCredentials()
511*/
512void QOAuth1::setTokenCredentials(const QString &token, const QString &tokenSecret)
513{
514 setToken(token);
515 setTokenSecret(tokenSecret);
516}
517
518/*!
519 Returns the url used to request temporary credentials to
520 start the authentication process.
521
522 \sa setTemporaryCredentialsUrl()
523*/
524QUrl QOAuth1::temporaryCredentialsUrl() const
525{
526 Q_D(const QOAuth1);
527 return d->temporaryCredentialsUrl;
528}
529
530/*!
531 Sets \a url as the URL to request temporary credentials to
532 start the authentication process.
533
534 \sa temporaryCredentialsUrl()
535*/
536void QOAuth1::setTemporaryCredentialsUrl(const QUrl &url)
537{
538 Q_D(QOAuth1);
539 if (d->temporaryCredentialsUrl != url) {
540 d->temporaryCredentialsUrl = url;
541 Q_EMIT temporaryCredentialsUrlChanged(url);
542 }
543}
544
545/*!
546 Returns the url used to request token credentials to continue
547 the authentication process.
548
549 \sa setTokenCredentialsUrl()
550*/
551QUrl QOAuth1::tokenCredentialsUrl() const
552{
553 Q_D(const QOAuth1);
554 return d->tokenCredentialsUrl;
555}
556
557/*!
558 Sets \a url as the URL to request the token credentials to
559 continue the authentication process.
560
561 \sa tokenCredentialsUrl()
562*/
563void QOAuth1::setTokenCredentialsUrl(const QUrl &url)
564{
565 Q_D(QOAuth1);
566 if (d->tokenCredentialsUrl != url) {
567 d->tokenCredentialsUrl = url;
568 Q_EMIT tokenCredentialsUrlChanged(url);
569 }
570}
571
572/*!
573 Returns the method used to sign the request to the web server.
574
575 \sa setSignatureMethod()
576*/
577QOAuth1::SignatureMethod QOAuth1::signatureMethod() const
578{
579 Q_D(const QOAuth1);
580 return d->signatureMethod;
581}
582
583/*!
584 Sets \a value as the method used to sign requests to the web
585 server.
586
587 \sa signatureMethod()
588*/
589void QOAuth1::setSignatureMethod(QOAuth1::SignatureMethod value)
590{
591 Q_D(QOAuth1);
592 if (d->signatureMethod != value) {
593 d->signatureMethod = value;
594 Q_EMIT signatureMethodChanged(value);
595 }
596}
597
598/*!
599 Sends an authenticated HEAD request and returns a new
600 QNetworkReply. The \a url and \a parameters are used to create
601 the request.
602
603 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.4}
604 {Hypertext Transfer Protocol -- HTTP/1.1: HEAD}
605*/
606QNetworkReply *QOAuth1::head(const QUrl &url, const QVariantMap &parameters)
607{
608 Q_D(QOAuth1);
609 if (!d->networkAccessManager()) {
610 qCWarning(d->loggingCategory, "QNetworkAccessManager not available");
611 return nullptr;
612 }
613 QNetworkRequest request(url);
614 setup(&request, parameters, QNetworkAccessManager::HeadOperation);
615 return d->networkAccessManager()->head(request);
616}
617
618/*!
619 Sends an authenticated GET request and returns a new
620 QNetworkReply. The \a url and \a parameters are used to create
621 the request.
622
623 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.3}
624 {Hypertext Transfer Protocol -- HTTP/1.1: GET}
625*/
626QNetworkReply *QOAuth1::get(const QUrl &url, const QVariantMap &parameters)
627{
628 Q_D(QOAuth1);
629 if (!d->networkAccessManager()) {
630 qCWarning(d->loggingCategory, "QNetworkAccessManager not available");
631 return nullptr;
632 }
633 QNetworkRequest request(url);
634 setup(&request, parameters, QNetworkAccessManager::GetOperation);
635 QNetworkReply *reply = d->networkAccessManager()->get(request);
636 connect(reply, &QNetworkReply::finished, [this, reply]() { emit finished(reply); });
637 return reply;
638}
639
640/*!
641 Sends an authenticated POST request and returns a new
642 QNetworkReply. The \a url and \a parameters are used to create
643 the request.
644
645 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.5}
646 {Hypertext Transfer Protocol -- HTTP/1.1: POST}
647*/
648QNetworkReply *QOAuth1::post(const QUrl &url, const QVariantMap &parameters)
649{
650 Q_D(QOAuth1);
651 if (!d->networkAccessManager()) {
652 qCWarning(d->loggingCategory, "QNetworkAccessManager not available");
653 return nullptr;
654 }
655 QNetworkRequest request(url);
656 setup(&request, parameters, QNetworkAccessManager::PostOperation);
657 d->addContentTypeHeaders(&request);
658
659 const QByteArray data = d->convertParameters(parameters);
660 QNetworkReply *reply = d->networkAccessManager()->post(request, data);
661 connect(reply, &QNetworkReply::finished, [this, reply]() { emit finished(reply); });
662 return reply;
663}
664
665/*!
666 Sends an authenticated PUT request and returns a new
667 QNetworkReply. The \a url and \a parameters are used to create
668 the request.
669
670 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.6}
671 {Hypertext Transfer Protocol -- HTTP/1.1: PUT}
672*/
673QNetworkReply *QOAuth1::put(const QUrl &url, const QVariantMap &parameters)
674{
675 Q_D(QOAuth1);
676 if (!d->networkAccessManager()) {
677 qCWarning(d->loggingCategory, "QNetworkAccessManager not available");
678 return nullptr;
679 }
680 QNetworkRequest request(url);
681 setup(&request, parameters, QNetworkAccessManager::PutOperation);
682 d->addContentTypeHeaders(&request);
683
684 const QByteArray data = d->convertParameters(parameters);
685 QNetworkReply *reply = d->networkAccessManager()->put(request, data);
686 connect(reply, &QNetworkReply::finished, std::bind(&QAbstractOAuth::finished, this, reply));
687 return reply;
688}
689
690/*!
691 Sends an authenticated DELETE request and returns a new
692 QNetworkReply. The \a url and \a parameters are used to create
693 the request.
694
695 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.7}
696 {Hypertext Transfer Protocol -- HTTP/1.1: DELETE}
697*/
698QNetworkReply *QOAuth1::deleteResource(const QUrl &url, const QVariantMap &parameters)
699{
700 Q_D(QOAuth1);
701 if (!d->networkAccessManager()) {
702 qCWarning(d->loggingCategory, "QNetworkAccessManager not available");
703 return nullptr;
704 }
705 QNetworkRequest request(url);
706 setup(&request, parameters, QNetworkAccessManager::DeleteOperation);
707 QNetworkReply *reply = d->networkAccessManager()->deleteResource(request);
708 connect(reply, &QNetworkReply::finished, [this, reply]() { emit finished(reply); });
709 return reply;
710}
711
712/*!
713 Starts the a request for temporary credentials using the request
714 method \a operation. The request URL is \a url and the
715 \a parameters shall encoded and sent during the request.
716
717 \b {See also}: \l {https://tools.ietf.org/html/rfc5849#section-2.1}
718 {The OAuth 1.0 Protocol: Temporary Credentials}
719*/
720QNetworkReply *QOAuth1::requestTemporaryCredentials(QNetworkAccessManager::Operation operation,
721 const QUrl &url,
722 const QVariantMap &parameters)
723{
724 Q_D(QOAuth1);
725 d->token.clear();
726 d->tokenSecret.clear();
727 QVariantMap allParameters(parameters);
728 allParameters.insert(Key::oauthCallback, callback());
729 return d->requestToken(operation, url, qMakePair(d->token, d->tokenSecret), allParameters);
730}
731
732/*!
733 Starts a request for token credentials using the request
734 method \a operation. The request URL is \a url and the
735 \a parameters shall be encoded and sent during the
736 request. The \a temporaryToken pair of string is used to identify
737 and sign the request.
738
739 \b {See also}: \l {https://tools.ietf.org/html/rfc5849#section-2.3}
740 {The OAuth 1.0 Protocol: Token Credentials}
741*/
742QNetworkReply *QOAuth1::requestTokenCredentials(QNetworkAccessManager::Operation operation,
743 const QUrl &url,
744 const QPair<QString, QString> &temporaryToken,
745 const QVariantMap &parameters)
746{
747 Q_D(QOAuth1);
748 d->tokenRequested = true;
749 return d->requestToken(operation, url, temporaryToken, parameters);
750}
751
752/*!
753 Signs the \a request using \a signingParameters and \a operation.
754
755 \overload
756*/
757void QOAuth1::setup(QNetworkRequest *request,
758 const QVariantMap &signingParameters,
759 QNetworkAccessManager::Operation operation)
760{
761 Q_D(const QOAuth1);
762
763 auto oauthParams = d->createOAuthBaseParams();
764
765 // Add signature parameter
766 {
767 QMultiMap<QString, QVariant> oauthParamsCopy(oauthParams);
768 const auto parameters = oauthParamsCopy.unite(signingParameters);
769 const auto signature = d->generateSignature(parameters, request->url(), operation);
770 oauthParams.insert(Key::oauthSignature, signature);
771 }
772
773 if (operation == QNetworkAccessManager::GetOperation) {
774 if (signingParameters.size()) {
775 QUrl url = request->url();
776 QUrlQuery query = QUrlQuery(url.query());
777 for (auto it = signingParameters.begin(), end = signingParameters.end(); it != end;
778 ++it)
779 query.addQueryItem(it.key(), it.value().toString());
780 url.setQuery(query);
781 request->setUrl(url);
782 }
783 }
784
785 request->setRawHeader("Authorization", generateAuthorizationHeader(oauthParams));
786
787 if (operation == QNetworkAccessManager::PostOperation
788 || operation == QNetworkAccessManager::PutOperation)
789 request->setHeader(QNetworkRequest::ContentTypeHeader,
790 QStringLiteral("application/x-www-form-urlencoded"));
791}
792
793/*!
794 \since 5.13
795
796 Signs the \a request using \a signingParameters and \a operationVerb.
797
798 \overload
799*/
800void QOAuth1::setup(QNetworkRequest *request, const QVariantMap &signingParameters, const QByteArray &operationVerb)
801{
802 Q_D(const QOAuth1);
803
804 auto oauthParams = d->createOAuthBaseParams();
805
806 // Add signature parameter
807 {
808 QMultiMap<QString, QVariant> oauthParamsCopy(oauthParams);
809 const auto parameters = oauthParamsCopy.unite(signingParameters);
810 const auto signature = d->generateSignature(parameters, request->url(), operationVerb);
811 oauthParams.insert(Key::oauthSignature, signature);
812 }
813
814 request->setRawHeader("Authorization", generateAuthorizationHeader(oauthParams));
815}
816
817/*!
818 Generates a nonce.
819
820 \b {See also}: \l {https://tools.ietf.org/html/rfc5849#section-3.3}
821 {The OAuth 1.0 Protocol: Nonce and Timestamp}
822*/
823QByteArray QOAuth1::nonce()
824{
825 return QAbstractOAuth::generateRandomString(8);
826}
827
828/*!
829 Generates an authorization header using \a oauthParams.
830
831 \b {See also}: \l {https://tools.ietf.org/html/rfc5849#section-3.5.1}
832 {The OAuth 1.0 Protocol: Authorization Header}
833*/
834QByteArray QOAuth1::generateAuthorizationHeader(const QVariantMap &oauthParams)
835{
836 // TODO Add realm parameter support
837 bool first = true;
838 QString ret(QStringLiteral("OAuth "));
839 QVariantMap headers(oauthParams);
840 for (auto it = headers.begin(), end = headers.end(); it != end; ++it) {
841 if (first)
842 first = false;
843 else
844 ret += QLatin1String(",");
845 ret += it.key() +
846 QLatin1String("=\"") +
847 QString::fromUtf8(QUrl::toPercentEncoding(it.value().toString())) +
848 QLatin1Char('\"');
849 }
850 return ret.toUtf8();
851}
852
853/*!
854 Starts the Redirection-Based Authorization flow.
855
856 \note For an out-of-band reply handler, a verifier string is
857 received after the call to this function; pass that to
858 continueGrantWithVerifier() to continue the grant process.
859
860 \sa continueGrantWithVerifier()
861
862 \b {See also}: \l {https://tools.ietf.org/html/rfc5849#section-2}
863 {The OAuth 1.0 Protocol: Redirection-Based Authorization}
864*/
865void QOAuth1::grant()
866{
867 Q_D(QOAuth1);
868 using Key = QOAuth1Private::OAuth1KeyString;
869
870 if (d->temporaryCredentialsUrl.isEmpty()) {
871 qCWarning(d->loggingCategory, "requestTokenUrl is empty");
872 return;
873 }
874 if (d->tokenCredentialsUrl.isEmpty()) {
875 qCWarning(d->loggingCategory, "authorizationGrantUrl is empty");
876 return;
877 }
878 if (!d->token.isEmpty() && status() == Status::Granted) {
879 qCWarning(d->loggingCategory, "Already authenticated");
880 return;
881 }
882
883 QMetaObject::Connection connection;
884 connection = connect(this, &QAbstractOAuth::statusChanged, [&](Status status) {
885 Q_D(QOAuth1);
886
887 if (status == Status::TemporaryCredentialsReceived) {
888 if (d->authorizationUrl.isEmpty()) {
889 // try upgrading token without verifier
890 auto reply = requestTokenCredentials(QNetworkAccessManager::PostOperation,
891 d->tokenCredentialsUrl,
892 qMakePair(d->token, d->tokenSecret));
893 connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
894 } else {
895 QVariantMap parameters;
896 parameters.insert(Key::oauthToken, d->token);
897 if (d->modifyParametersFunction)
898 d->modifyParametersFunction(Stage::RequestingAuthorization, &parameters);
899
900 // https://tools.ietf.org/html/rfc5849#section-2.2
901 resourceOwnerAuthorization(d->authorizationUrl, parameters);
902 }
903 } else if (status == Status::NotAuthenticated) {
904 // Inherit class called QAbstractOAuth::setStatus(Status::NotAuthenticated);
905 setTokenCredentials(QString(), QString());
906 disconnect(connection);
907 }
908 });
909
910 auto httpReplyHandler = qobject_cast<QOAuthHttpServerReplyHandler*>(replyHandler());
911 if (httpReplyHandler) {
912 connect(httpReplyHandler, &QOAuthHttpServerReplyHandler::callbackReceived, [&](
913 const QVariantMap &values) {
914 QString verifier = values.value(Key::oauthVerifier).toString();
915 if (verifier.isEmpty()) {
916 qCWarning(d->loggingCategory, "%s not found in the callback",
917 qPrintable(Key::oauthVerifier));
918 return;
919 }
920 continueGrantWithVerifier(verifier);
921 });
922 }
923
924 // requesting temporary credentials
925 auto reply = requestTemporaryCredentials(QNetworkAccessManager::PostOperation,
926 d->temporaryCredentialsUrl);
927 connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
928}
929
930/*!
931 Continues the Redirection-Based Authorization flow using
932 \a verifier. Call this function when using an Out-of-band reply
933 handler to supply the verifier provided by the web server.
934*/
935void QOAuth1::continueGrantWithVerifier(const QString &verifier)
936{
937 // https://tools.ietf.org/html/rfc5849#section-2.3
938 Q_D(QOAuth1);
939
940 QVariantMap parameters;
941 parameters.insert(Key::oauthVerifier, verifier);
942 auto reply = requestTokenCredentials(QNetworkAccessManager::PostOperation,
943 d->tokenCredentialsUrl,
944 qMakePair(d->token, d->tokenSecret),
945 parameters);
946 connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
947}
948
949QT_END_NAMESPACE
950
951#endif // QT_NO_HTTP
952

source code of qtnetworkauth/src/oauth/qoauth1.cpp