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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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