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 | |
21 | QT_BEGIN_NAMESPACE |
22 | |
23 | using 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 | |
74 | using OAuth1 = QOAuth1Private::OAuth1KeyString; |
75 | const QString OAuth1::oauthCallback = u"oauth_callback"_s; |
76 | const QString OAuth1::oauthCallbackConfirmed = u"oauth_callback_confirmed"_s; |
77 | const QString OAuth1::oauthConsumerKey = u"oauth_consumer_key"_s; |
78 | const QString OAuth1::oauthNonce = u"oauth_nonce"_s; |
79 | const QString OAuth1::oauthSignature = u"oauth_signature"_s; |
80 | const QString OAuth1::oauthSignatureMethod = u"oauth_signature_method"_s; |
81 | const QString OAuth1::oauthTimestamp = u"oauth_timestamp"_s; |
82 | const QString OAuth1::oauthToken = u"oauth_token"_s; |
83 | const QString OAuth1::oauthTokenSecret = u"oauth_token_secret"_s; |
84 | const QString OAuth1::oauthVerifier = u"oauth_verifier"_s; |
85 | const QString OAuth1::oauthVersion = u"oauth_version"_s; |
86 | |
87 | QOAuth1Private::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 | |
99 | void 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 | |
110 | void 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 | |
127 | QNetworkReply *QOAuth1Private::requestToken(QNetworkAccessManager::Operation operation, |
128 | const QUrl &url, |
129 | const std::pair<QString, QString> &token, |
130 | const QVariantMap ¶meters) |
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 | |
198 | QString 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 | |
213 | QByteArray QOAuth1Private::generateSignature(const QMultiMap<QString, QVariant> ¶meters, |
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 | |
225 | QByteArray QOAuth1Private::generateSignature(const QMultiMap<QString, QVariant> ¶meters, |
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 | |
238 | QByteArray 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 | |
251 | QVariantMap 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 | */ |
270 | void 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 | |
285 | void 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 | |
292 | void 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 | */ |
329 | QOAuth1::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 | */ |
338 | QOAuth1::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 | */ |
351 | QOAuth1::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 | */ |
366 | QString 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 | */ |
378 | void 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 | */ |
393 | std::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 | */ |
407 | void 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 | */ |
420 | void 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 | */ |
433 | QString 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 | */ |
444 | void 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 | */ |
461 | std::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 | */ |
475 | void 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 | */ |
490 | void 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 | */ |
502 | QUrl 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 | */ |
514 | void 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 | */ |
529 | QUrl 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 | */ |
541 | void 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 | */ |
555 | QOAuth1::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 | */ |
567 | void 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 | */ |
584 | QNetworkReply *QOAuth1::head(const QUrl &url, const QVariantMap ¶meters) |
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 | */ |
604 | QNetworkReply *QOAuth1::get(const QUrl &url, const QVariantMap ¶meters) |
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 | */ |
626 | QNetworkReply *QOAuth1::post(const QUrl &url, const QVariantMap ¶meters) |
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 | */ |
651 | QNetworkReply *QOAuth1::put(const QUrl &url, const QVariantMap ¶meters) |
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 | */ |
676 | QNetworkReply *QOAuth1::deleteResource(const QUrl &url, const QVariantMap ¶meters) |
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 | */ |
698 | QNetworkReply *QOAuth1::requestTemporaryCredentials(QNetworkAccessManager::Operation operation, |
699 | const QUrl &url, |
700 | const QVariantMap ¶meters) |
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 ¶meters) |
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 | */ |
725 | QNetworkReply *QOAuth1::requestTokenCredentials(QNetworkAccessManager::Operation operation, |
726 | const QUrl &url, |
727 | const std::pair<QString, QString> &temporaryToken, |
728 | const QVariantMap ¶meters) |
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 | */ |
740 | void 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 | */ |
783 | void 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 | */ |
806 | QByteArray 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 | */ |
817 | QByteArray 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 | */ |
848 | void 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, ¶meters); |
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 | */ |
917 | void 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 | |
931 | QT_END_NAMESPACE |
932 |
Definitions
- oauthCallback
- oauthCallbackConfirmed
- oauthConsumerKey
- oauthNonce
- oauthSignature
- oauthSignatureMethod
- oauthTimestamp
- oauthToken
- oauthTokenSecret
- oauthVerifier
- oauthVersion
- QOAuth1Private
- appendCommonHeaders
- appendSignature
- requestToken
- signatureMethodString
- generateSignature
- generateSignature
- formatSignature
- createOAuthBaseParams
- prepareRequest
- _q_onTokenRequestError
- _q_tokensReceived
- QOAuth1
- QOAuth1
- QOAuth1
- clientSharedSecret
- setClientSharedSecret
- clientCredentials
- setClientCredentials
- setClientCredentials
- tokenSecret
- setTokenSecret
- tokenCredentials
- setTokenCredentials
- setTokenCredentials
- temporaryCredentialsUrl
- setTemporaryCredentialsUrl
- tokenCredentialsUrl
- setTokenCredentialsUrl
- signatureMethod
- setSignatureMethod
- head
- get
- post
- put
- deleteResource
- requestTemporaryCredentials
- requestTokenCredentials
- setup
- setup
- nonce
- generateAuthorizationHeader
- grant
Learn to use CMake with our Intro Training
Find out more