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

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