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