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 | |
26 | QT_BEGIN_NAMESPACE |
27 | |
28 | using 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 | |
79 | using OAuth1 = QOAuth1Private::OAuth1KeyString; |
80 | const QString OAuth1::oauthCallback = u"oauth_callback"_s ; |
81 | const QString OAuth1::oauthCallbackConfirmed = u"oauth_callback_confirmed"_s ; |
82 | const QString OAuth1::oauthConsumerKey = u"oauth_consumer_key"_s ; |
83 | const QString OAuth1::oauthNonce = u"oauth_nonce"_s ; |
84 | const QString OAuth1::oauthSignature = u"oauth_signature"_s ; |
85 | const QString OAuth1::oauthSignatureMethod = u"oauth_signature_method"_s ; |
86 | const QString OAuth1::oauthTimestamp = u"oauth_timestamp"_s ; |
87 | const QString OAuth1::oauthToken = u"oauth_token"_s ; |
88 | const QString OAuth1::oauthTokenSecret = u"oauth_token_secret"_s ; |
89 | const QString OAuth1::oauthVerifier = u"oauth_verifier"_s ; |
90 | const QString OAuth1::oauthVersion = u"oauth_version"_s ; |
91 | |
92 | QOAuth1Private::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 | |
104 | void QOAuth1Private::(QVariantMap *) |
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 | |
115 | void QOAuth1Private::appendSignature(QAbstractOAuth::Stage stage, |
116 | QVariantMap *, |
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 | |
132 | QNetworkReply *QOAuth1Private::requestToken(QNetworkAccessManager::Operation operation, |
133 | const QUrl &url, |
134 | const QPair<QString, QString> &token, |
135 | const QVariantMap ¶meters) |
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 ; |
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 | |
203 | QString 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 | |
218 | QByteArray QOAuth1Private::generateSignature(const QMultiMap<QString, QVariant> ¶meters, |
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 | |
230 | QByteArray QOAuth1Private::generateSignature(const QMultiMap<QString, QVariant> ¶meters, |
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 | |
243 | QByteArray 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 | |
256 | QVariantMap 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 | */ |
275 | void 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 | |
289 | void 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 | |
296 | void 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 | */ |
333 | QOAuth1::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 | */ |
342 | QOAuth1::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 | */ |
355 | QOAuth1::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 | */ |
370 | QString 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 | */ |
382 | void 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 | */ |
397 | QPair<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 | */ |
409 | void 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 | */ |
422 | void 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 | */ |
435 | QString 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 | */ |
446 | void 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 | */ |
461 | QPair<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 | */ |
473 | void 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 | */ |
488 | void 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 | */ |
500 | QUrl 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 | */ |
512 | void 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 | */ |
527 | QUrl 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 | */ |
539 | void 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 | */ |
553 | QOAuth1::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 | */ |
565 | void 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 | */ |
582 | QNetworkReply *QOAuth1::(const QUrl &url, const QVariantMap ¶meters) |
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 | */ |
602 | QNetworkReply *QOAuth1::get(const QUrl &url, const QVariantMap ¶meters) |
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 | */ |
624 | QNetworkReply *QOAuth1::post(const QUrl &url, const QVariantMap ¶meters) |
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 | */ |
649 | QNetworkReply *QOAuth1::put(const QUrl &url, const QVariantMap ¶meters) |
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 | */ |
674 | QNetworkReply *QOAuth1::deleteResource(const QUrl &url, const QVariantMap ¶meters) |
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 | */ |
696 | QNetworkReply *QOAuth1::requestTemporaryCredentials(QNetworkAccessManager::Operation operation, |
697 | const QUrl &url, |
698 | const QVariantMap ¶meters) |
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 | */ |
718 | QNetworkReply *QOAuth1::requestTokenCredentials(QNetworkAccessManager::Operation operation, |
719 | const QUrl &url, |
720 | const QPair<QString, QString> &temporaryToken, |
721 | const QVariantMap ¶meters) |
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 | */ |
733 | void 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 | */ |
776 | void 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 | */ |
799 | QByteArray 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 | */ |
810 | QByteArray QOAuth1::(const QVariantMap &oauthParams) |
811 | { |
812 | // TODO Add realm parameter support |
813 | bool first = true; |
814 | QString ret(QStringLiteral("OAuth " )); |
815 | QVariantMap (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 | */ |
841 | void 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, ¶meters); |
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 | */ |
909 | void 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 | |
923 | QT_END_NAMESPACE |
924 | |
925 | #endif // QT_NO_HTTP |
926 | |