1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:critical reason:authorization-protocol
4
5#include <qabstractoauth.h>
6#include <qabstractoauthreplyhandler.h>
7
8#include <private/qabstractoauth_p.h>
9
10#include <QtCore/qurl.h>
11#include <QtCore/qstring.h>
12#include <QtCore/qurlquery.h>
13#include <QtCore/qjsondocument.h>
14#include <QtCore/qmessageauthenticationcode.h>
15
16#include <QtNetwork/qnetworkrequest.h>
17#include <QtNetwork/qnetworkaccessmanager.h>
18#include <QtNetwork/qnetworkreply.h>
19
20#include <QtCore/qrandom.h>
21#include <QtCore/private/qlocking_p.h>
22
23QT_BEGIN_NAMESPACE
24
25/*!
26 \class QAbstractOAuth
27 \inmodule QtNetworkAuth
28 \ingroup oauth
29 \brief The QAbstractOAuth class is the base of all
30 implementations of OAuth authentication methods.
31 \since 5.8
32
33 The class defines the basic interface of the OAuth
34 authentication classes. By inheriting this class, you
35 can create custom authentication methods for different web
36 services.
37
38 It also contains some functions to ease the process of
39 implementing different authentication flows.
40*/
41
42/*!
43 \enum QAbstractOAuth::Status
44
45 Indicates the current authentication status.
46
47 \value NotAuthenticated No token has been
48 retrieved.
49
50 \value TemporaryCredentialsReceived Temporary credentials
51 have been received, this status is used in some OAuth
52 authetication methods.
53
54 \value Granted Token credentials have
55 been received and authenticated calls are allowed.
56
57 \value RefreshingToken New token credentials
58 have been requested.
59*/
60
61/*!
62 \enum QAbstractOAuth::Stage
63
64 Identifies an authentication stage. It's passed to a
65 modifyParametersFunction so that it can make different changes to
66 parameters at each call to it during the process of
67 authentication.
68
69 \value RequestingTemporaryCredentials Preparing the temporary
70 credentials request.
71
72 \value RequestingAuthorization Preparing the
73 authorization grant URL.
74
75 \value RequestingAccessToken Preparing the token
76 request.
77
78 \value RefreshingAccessToken Preparing the access
79 token refresh.
80*/
81
82/*!
83 \enum QAbstractOAuth::Error
84
85 Indicates the latest received error.
86
87 \value NoError No error has ocurred.
88
89 \value NetworkError Failed to connect to the server.
90
91 \value ServerError The server answered the
92 request with an error, or its response was not successfully received
93 (for example, due to a state mismatch).
94
95 \value OAuthTokenNotFoundError The server's response to
96 a token request provided no token identifier.
97
98 \value OAuthTokenSecretNotFoundError The server's response to
99 a token request provided no token secret.
100
101 \value OAuthCallbackNotVerified The authorization server
102 has not verified the supplied callback URI in the request. This
103 usually happens when the provided callback does not match with
104 the callback supplied during client registration.
105
106 \value [since 6.9] ClientError An error that is attributable
107 to the client application (e.g. missing configuration or attempting a
108 request in a state where it's not allowed). Currently used by
109 \l {QOAuth2DeviceAuthorizationFlow}.
110
111 \value [since 6.9] ExpiredError A token has expired.
112 Currently used by \l {QOAuth2DeviceAuthorizationFlow}.
113*/
114
115// ### Qt 7 remove ContentType when removing support for HTTP methods (QTBUG-124329)
116
117/*!
118 \enum QAbstractOAuth::ContentType
119
120 Indicates the MIME Content-Type of the POST methods in
121 authenticated calls.
122
123 \value WwwFormUrlEncoded Uses
124 application/x-www-form-urlencoded format.
125
126 \value Json Uses
127 application/json format.
128*/
129
130/*!
131 \property QAbstractOAuth::status
132 \brief This property holds the current authentication status.
133*/
134
135/*!
136 \property QAbstractOAuth::extraTokens
137 \brief This property holds the extra tokens received from the
138 server.
139*/
140
141/*!
142 \property QAbstractOAuth::authorizationUrl
143 \brief This property holds the URL used to request the Resource
144 Owner Authorization as described in:
145 \l{https://tools.ietf.org/html/rfc5849#section-2.2}{The OAuth
146 1.0 Protocol: Resource Owner Authorization}
147*/
148
149/*!
150 \property QAbstractOAuth::contentType
151 \brief The Content-Type to use when sending authorization
152 parameters.
153
154 This property controls how parameters are formatted when sent
155 with a POST request. A suitable header is also added.
156*/
157
158/*!
159 \fn void QAbstractOAuth::authorizeWithBrowser(const QUrl &url)
160
161 This signal is emitted when the \a url generated by
162 resourceOwnerAuthorization() is ready to be used in the web
163 browser to allow the application to impersonate the user.
164 \sa resourceOwnerAuthorization()
165*/
166
167/*!
168 \fn void QAbstractOAuth::granted()
169
170 This signal is emitted when the authorization flow finishes
171 successfully.
172*/
173
174/*!
175 \fn void QAbstractOAuth::requestFailed(const QAbstractOAuth::Error error)
176
177 This signal is emitted to indicate that a request to a server has failed.
178 The \a error supplied indicates how the request failed.
179
180 \sa QAbstractOAuth2::error()
181 \sa QAbstractOAuthReplyHandler::tokenRequestErrorOccurred()
182*/
183
184// ### Qt 7 remove the support for separate HTTP methods (head(), get(), post(), put(),
185// deleteResource()), see QTBUG-124329
186
187/*!
188 \fn QNetworkReply *QAbstractOAuth::head(const QUrl &url, const QVariantMap &parameters)
189
190 Sends an authenticated HEAD request and returns a new
191 QNetworkReply. The \a url and \a parameters are used to create
192 the request.
193
194 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.4}
195 {Hypertext Transfer Protocol -- HTTP/1.1: HEAD}
196*/
197
198/*!
199 \fn QNetworkReply *QAbstractOAuth::get(const QUrl &url, const QVariantMap &parameters)
200
201 Sends an authenticated GET request and returns a new
202 QNetworkReply. The \a url and \a parameters are used to create
203 the request.
204
205 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.3}
206 {Hypertext Transfer Protocol -- HTTP/1.1: GET}
207*/
208
209/*!
210 \fn QNetworkReply *QAbstractOAuth::post(const QUrl &url, const QVariantMap &parameters)
211
212 Sends an authenticated POST request and returns a new
213 QNetworkReply. The \a url and \a parameters are used to create
214 the request.
215
216 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.5}
217 {Hypertext Transfer Protocol -- HTTP/1.1: POST}
218*/
219
220/*!
221 \fn QNetworkReply *QAbstractOAuth::put(const QUrl &url, const QVariantMap &parameters)
222
223 Sends an authenticated PUT request and returns a new
224 QNetworkReply. The \a url and \a parameters are used to create
225 the request.
226
227 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.6}
228 {Hypertext Transfer Protocol -- HTTP/1.1: PUT}
229*/
230
231/*!
232 \fn QNetworkReply *QAbstractOAuth::deleteResource(const QUrl &url, const QVariantMap &parameters)
233
234 Sends an authenticated DELETE request and returns a new
235 QNetworkReply. The \a url and \a parameters are used to create
236 the request.
237
238 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.7}
239 {Hypertext Transfer Protocol -- HTTP/1.1: DELETE}
240*/
241
242/*!
243 \fn void QAbstractOAuth::grant()
244
245 Override this function to implement the corresponding
246 authentication flow in the subclasses. Client code calls this
247 function to start the authentication workflow. This may require
248 user interaction: for example, asking the user's authorization
249 via a web browser. When the authentication succeeds, it should
250 emit granted(); this gives notice that credentials are ready to
251 be used in authenticated calls.
252*/
253
254QAbstractOAuthPrivate::QAbstractOAuthPrivate(const char *loggingCategory,
255 const QUrl &authorizationUrl,
256 const QString &clientIdentifier,
257 QNetworkAccessManager *manager) :
258 loggingCategory(loggingCategory),
259 clientIdentifier(clientIdentifier),
260 authorizationUrl(authorizationUrl),
261 defaultReplyHandler(new QOAuthOobReplyHandler),
262 networkAccessManagerPointer(manager)
263{}
264
265QAbstractOAuthPrivate::~QAbstractOAuthPrivate()
266{}
267
268QNetworkAccessManager *QAbstractOAuthPrivate::networkAccessManager()
269{
270 Q_Q(QAbstractOAuth);
271 if (!networkAccessManagerPointer)
272 networkAccessManagerPointer = new QNetworkAccessManager(q);
273 return networkAccessManagerPointer.data();
274}
275
276void QAbstractOAuthPrivate::setStatus(QAbstractOAuth::Status newStatus)
277{
278 Q_Q(QAbstractOAuth);
279 if (status != newStatus) {
280 status = newStatus;
281 Q_EMIT q->statusChanged(status);
282 if (status == QAbstractOAuth::Status::Granted)
283 Q_EMIT q->granted();
284 }
285}
286
287QByteArray QAbstractOAuthPrivate::generateRandomBase64String(quint8 length)
288{
289 // We'll use QByteArray::toBase64() to create a random-looking string from
290 // pure random data. In Base64 encoding, we get 6 bits of randomness per
291 // character, so at most 255 * 6 bits are needed in this function.
292 using Word = QRandomGenerator::result_type;
293 auto wordCountForLength = [](int len) constexpr {
294 constexpr int BitsPerWord = std::numeric_limits<Word>::digits;
295 int bitcount = len * 6;
296 return (bitcount + BitsPerWord - 1) / BitsPerWord;
297 };
298 constexpr int RandomBufferLength = wordCountForLength(std::numeric_limits<quint8>::max());
299 Word randomdata[RandomBufferLength];
300
301 qsizetype randomlen = wordCountForLength(length);
302 QRandomGenerator::system()->fillRange(buffer: randomdata, count: randomlen);
303 QByteArray ba = QByteArray::fromRawData(data: reinterpret_cast<char *>(randomdata),
304 size: randomlen * sizeof(quint32))
305 .toBase64(options: QByteArray::Base64UrlEncoding);
306 ba.truncate(pos: length); // toBase64 output length has fixed lengths: 6, 11, 16, 22, 27...
307 return ba;
308}
309
310void QAbstractOAuthPrivate::setExtraTokens(const QVariantMap &tokens)
311{
312 if (extraTokens == tokens)
313 return;
314 Q_Q(QAbstractOAuth);
315 extraTokens = tokens;
316 emit q->extraTokensChanged(tokens: extraTokens);
317}
318
319// ### Qt 7 remove when removing HTTP method support (QTBUG-124329)
320QByteArray QAbstractOAuthPrivate::convertParameters(const QVariantMap &parameters)
321{
322 QByteArray data;
323 switch (contentType) {
324 case QAbstractOAuth::ContentType::Json:
325 data = QJsonDocument::fromVariant(variant: QVariant(parameters)).toJson();
326 break;
327 case QAbstractOAuth::ContentType::WwwFormUrlEncoded: {
328 QUrlQuery query;
329 for (auto it = parameters.begin(), end = parameters.end(); it != end; ++it)
330 query.addQueryItem(key: it.key(), value: it->toString());
331 data = query.toString(encoding: QUrl::FullyEncoded).toLatin1();
332 break;
333 }
334 }
335 return data;
336}
337
338// ### Qt 7 remove when removing HTTP method support (QTBUG-124329)
339void QAbstractOAuthPrivate::addContentTypeHeaders(QNetworkRequest *request)
340{
341 Q_ASSERT(request);
342
343 switch (contentType) {
344 case QAbstractOAuth::ContentType::WwwFormUrlEncoded:
345 request->setHeader(header: QNetworkRequest::ContentTypeHeader,
346 QStringLiteral("application/x-www-form-urlencoded"));
347 break;
348 case QAbstractOAuth::ContentType::Json:
349 request->setHeader(header: QNetworkRequest::ContentTypeHeader,
350 QStringLiteral("application/json"));
351 break;
352 }
353}
354
355QUrlQuery QAbstractOAuthPrivate::createQuery(const QMultiMap<QString, QVariant> &parameters)
356{
357 QUrlQuery query;
358 for (auto it = parameters.begin(), end = parameters.end(); it != end; ++it)
359 query.addQueryItem(key: it.key(), value: it.value().toString());
360 return query;
361}
362
363QAbstractOAuth::QAbstractOAuth(QAbstractOAuthPrivate &dd, QObject *parent)
364 : QObject(dd, parent)
365{
366 qRegisterMetaType<QAbstractOAuth::Error>();
367}
368
369/*!
370 Destroys the abstract OAuth.
371*/
372QAbstractOAuth::~QAbstractOAuth()
373{}
374
375/*!
376 Returns the current client identifier used in the authentication
377 process.
378
379 \sa setClientIdentifier()
380*/
381QString QAbstractOAuth::clientIdentifier() const
382{
383 Q_D(const QAbstractOAuth);
384 return d->clientIdentifier;
385}
386
387/*!
388 Sets the current client identifier to \a clientIdentifier.
389
390 \sa clientIdentifier()
391*/
392void QAbstractOAuth::setClientIdentifier(const QString &clientIdentifier)
393{
394 Q_D(QAbstractOAuth);
395 if (d->clientIdentifier != clientIdentifier) {
396 d->clientIdentifier = clientIdentifier;
397 Q_EMIT clientIdentifierChanged(clientIdentifier);
398 }
399}
400
401/*!
402 Returns the token used to sign the authenticated requests.
403
404 \sa setToken()
405*/
406QString QAbstractOAuth::token() const
407{
408 Q_D(const QAbstractOAuth);
409 return d->token;
410}
411
412/*!
413 Sets the token used to sign authenticated requests to \a token.
414
415 \sa token()
416*/
417void QAbstractOAuth::setToken(const QString &token)
418{
419 Q_D(QAbstractOAuth);
420 if (d->token != token) {
421 d->token = token;
422 Q_EMIT tokenChanged(token);
423 }
424}
425
426/*!
427 Returns the current network access manager used to send the
428 requests to the server during authentication flows or to make
429 authentication calls.
430
431 \sa setNetworkAccessManager(), QNetworkAccessManager
432*/
433QNetworkAccessManager *QAbstractOAuth::networkAccessManager() const
434{
435 Q_D(const QAbstractOAuth);
436 return d->networkAccessManagerPointer.data();
437}
438
439/*!
440 Sets the network manager to \a networkAccessManager.
441 QAbstractOAuth does not take ownership of
442 \a networkAccessManager. If no custom network access manager is
443 set, an internal network access manager is used.
444 This network access manager will be used
445 to make the request to the authentication server and the
446 authenticated request to the web service.
447
448 \sa networkAccessManager(), QNetworkAccessManager
449*/
450void QAbstractOAuth::setNetworkAccessManager(QNetworkAccessManager *networkAccessManager)
451{
452 Q_D(QAbstractOAuth);
453 if (networkAccessManager != d->networkAccessManagerPointer) {
454 if (d->networkAccessManagerPointer && d->networkAccessManagerPointer->parent() == this)
455 delete d->networkAccessManagerPointer.data();
456 d->networkAccessManagerPointer = networkAccessManager;
457 }
458}
459
460/*!
461 Returns the current authentication status.
462 \sa Status
463*/
464QAbstractOAuth::Status QAbstractOAuth::status() const
465{
466 Q_D(const QAbstractOAuth);
467 return d->status;
468}
469
470/*!
471 Returns the authorization request URL.
472 \sa setAuthorizationUrl()
473*/
474QUrl QAbstractOAuth::authorizationUrl() const
475{
476 Q_D(const QAbstractOAuth);
477 return d->authorizationUrl;
478}
479
480/*!
481 Sets the authorization request URL to \a url. This address
482 will be used to allow the user to grant the application the
483 ability to make authenticated calls on behalf of the user.
484 \sa authorizationUrl()
485*/
486void QAbstractOAuth::setAuthorizationUrl(const QUrl &url)
487{
488 Q_D(QAbstractOAuth);
489 if (d->authorizationUrl != url) {
490 d->authorizationUrl = url;
491 Q_EMIT authorizationUrlChanged(url);
492 }
493}
494
495/*!
496 Sets the current status to \a status. This method is for use
497 by classes based on QAbstractOAuth.
498 \sa status()
499*/
500void QAbstractOAuth::setStatus(QAbstractOAuth::Status status)
501{
502 Q_D(QAbstractOAuth);
503 if (status != d->status) {
504 d->status = status;
505 Q_EMIT statusChanged(status);
506 }
507}
508
509/*!
510 Returns the reply handler currently in use.
511 \sa setReplyHandler(), QAbstractOAuthReplyHandler
512*/
513QAbstractOAuthReplyHandler *QAbstractOAuth::replyHandler() const
514{
515 Q_D(const QAbstractOAuth);
516 return d->replyHandler ? d->replyHandler.data() : d->defaultReplyHandler.data();
517}
518
519/*!
520 Sets the current reply handler to \a handler.
521 \note Does not take ownership of \a handler.
522*/
523void QAbstractOAuth::setReplyHandler(QAbstractOAuthReplyHandler *handler)
524{
525 Q_D(QAbstractOAuth);
526 d->replyHandler = handler;
527}
528
529// ### Qt 7 remove prepareRequest when removing HTTP method support (QTBUG-124329)
530
531/*!
532 \fn QAbstractOAuth::prepareRequest(QNetworkRequest *request, const QByteArray &verb, const QByteArray &body)
533 \since 5.13
534
535 Authorizes the given \a request by adding a header and \a body to
536 it required for authenticated requests.
537
538 The \a verb must be a valid HTTP verb and the same as the one that will be
539 used to send the \a request.
540*/
541
542/*!
543 Returns the current parameter-modification function.
544 \sa setModifyParametersFunction(), Stage
545*/
546QAbstractOAuth::ModifyParametersFunction QAbstractOAuth::modifyParametersFunction() const
547{
548 Q_D(const QAbstractOAuth);
549 return d->modifyParametersFunction;
550}
551
552/*!
553 Sets the parameter-modification function \a modifyParametersFunction.
554 This function is used to customize the parameters sent to the server
555 during a specified authorization stage. The number of calls to this
556 function depends on the flow used during the authentication.
557 \sa modifyParametersFunction(), Stage
558*/
559void QAbstractOAuth::setModifyParametersFunction(
560 const QAbstractOAuth::ModifyParametersFunction &modifyParametersFunction)
561{
562 Q_D(QAbstractOAuth);
563 d->modifyParametersFunction = modifyParametersFunction;
564}
565
566/*!
567 Returns the current Content-Type used in authenticated calls.
568 \sa setContentType(), post()
569*/
570QAbstractOAuth::ContentType QAbstractOAuth::contentType() const
571{
572 // ### Qt 7 remove this function when removing HTTP method support (QTBUG-124329)
573 Q_D(const QAbstractOAuth);
574 return d->contentType;
575}
576
577/*!
578 Sets the current Content-Type to \a contentType.
579*/
580void QAbstractOAuth::setContentType(QAbstractOAuth::ContentType contentType)
581{
582 // ### Qt 7 remove this function when removing HTTP method support (QTBUG-124329)
583 Q_D(QAbstractOAuth);
584 if (d->contentType != contentType) {
585 d->contentType = contentType;
586 Q_EMIT contentTypeChanged(contentType);
587 }
588}
589
590/*!
591 Returns the extra tokens received from the server during
592 authentication.
593 \sa extraTokensChanged()
594*/
595QVariantMap QAbstractOAuth::extraTokens() const
596{
597 Q_D(const QAbstractOAuth);
598 return d->extraTokens;
599}
600
601/*!
602 Returns the current callback string corresponding to the
603 current reply handler. The returned string is the string
604 sent to the server to specify the callback URI, or the word
605 identifying the alternative method in headless devices.
606 \sa replyHandler(), setReplyHandler()
607*/
608QString QAbstractOAuth::callback() const
609{
610 Q_D(const QAbstractOAuth);
611 return d->replyHandler ? d->replyHandler->callback()
612 : d->defaultReplyHandler->callback();
613}
614
615/*!
616 Builds the resource owner authorization URL to be used in the web
617 browser: \a url is used as the base URL and the query is created
618 using \a parameters. When the URL is ready, the
619 authorizeWithBrowser() signal will be emitted with the generated
620 URL.
621 \sa authorizeWithBrowser()
622*/
623void QAbstractOAuth::resourceOwnerAuthorization(const QUrl &url, const QMultiMap<QString, QVariant> &parameters)
624{
625 QUrl u = url;
626 u.setQuery(QAbstractOAuthPrivate::createQuery(parameters));
627 Q_EMIT authorizeWithBrowser(url: u);
628}
629
630/*!
631 \threadsafe
632 Generates a random string which could be used as state or nonce.
633 The parameter \a length determines the size of the generated
634 string.
635
636 \b {See also}: \l {https://tools.ietf.org/html/rfc5849#section-3.3}{The
637 OAuth 1.0 Protocol: Nonce and Timestamp}.
638*/
639QByteArray QAbstractOAuth::generateRandomString(quint8 length)
640{
641 return QAbstractOAuthPrivate::generateRandomBase64String(length);
642}
643
644QT_END_NAMESPACE
645

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