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

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