1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <qabstractoauth2.h>
5#include <private/qabstractoauth2_p.h>
6
7#include <QtCore/qurl.h>
8#include <QtCore/qurlquery.h>
9#include <QtCore/qbytearray.h>
10#include <QtCore/qmessageauthenticationcode.h>
11
12#include <QtNetwork/qnetworkreply.h>
13#include <QtNetwork/qnetworkrequest.h>
14#include <QtNetwork/qnetworkaccessmanager.h>
15#include <QtNetwork/qhttpmultipart.h>
16
17#ifndef QT_NO_SSL
18#include <QtNetwork/qsslconfiguration.h>
19#endif
20
21QT_BEGIN_NAMESPACE
22
23using namespace Qt::StringLiterals;
24
25/*!
26 \class QAbstractOAuth2
27 \inmodule QtNetworkAuth
28 \ingroup oauth
29 \brief The QAbstractOAuth2 class is the base of all
30 implementations of OAuth 2 authentication methods.
31 \since 5.8
32
33 The class defines the basic interface of the OAuth 2
34 authentication classes. By inheriting this class, you
35 can create custom authentication methods using the OAuth 2
36 standard for different web services.
37
38 A description of how OAuth 2 works can be found in:
39 \l {https://tools.ietf.org/html/rfc6749}{The OAuth 2.0
40 Authorization Framework}
41*/
42
43/*!
44 \page oauth-http-method-alternatives
45 \title OAuth2 HTTP method alternatives
46 \brief This page provides alternatives for QtNetworkAuth
47 OAuth2 HTTP methods.
48
49 QtNetworkAuth provides HTTP Methods such as \l {QAbstractOAuth::get()}
50 for issuing authenticated requests. In the case of OAuth2,
51 this typically means setting the
52 \l {QHttpHeaders::WellKnownHeader}{Authorization} header, as
53 specified in \l {https://datatracker.ietf.org/doc/html/rfc6750#section-2.1}
54 {RFC 6750}.
55
56 Since this operation is straightforward to do, it is better to use
57 the normal QtNetwork HTTP method APIs directly, and set this header
58 manually. These QtNetwork APIs have less assumptions on the message
59 content types and provide a broader set of APIs.
60
61 See \l QRestAccessManager, \l QNetworkAccessManager, QNetworkRequest,
62 QNetworkRequestFactory.
63
64 \section1 QNetworkRequest
65
66 The needed \e Authorization header can be set directly on each
67 request needing authorization.
68
69 \code
70 using namespace Qt::StringLiterals;
71
72 QOAuth2AuthorizationCodeFlow m_oauth;
73 QNetworkRequest request;
74
75 QHttpHeaders headers;
76 headers.append(QHttpHeaders::WellKnownHeader::Authorization, u"Bearer "_s + m_oauth.token());
77 request.setHeaders(headers);
78 \endcode
79
80 After setting the header, use the request normally with either
81 \l QRestAccessManager or \l QNetworkAccessManager.
82
83 \section1 QNetworkRequestFactory
84
85 QNetworkRequestFactory is a convenience class introduced in Qt 6.7.
86 It provides a suitable method for this task:
87 \l {QNetworkRequestFactory::setBearerToken()}, as illustrated
88 by the code below.
89
90 \code
91 QNetworkRequestFactory m_api({"https://www.example.com/v3"});
92 QOAuth2AuthorizationCodeFlow m_oauth;
93 // ...
94 connect(&m_oauth, &QOAuth2AuthorizationCodeFlow::granted, this, [this]{
95 m_api.setBearerToken(m_oauth.token().toLatin1());
96 });
97 \endcode
98
99 After setting the bearer token, use the request factory normally
100 with either \l QRestAccessManager or \l QNetworkAccessManager.
101*/
102
103/*!
104 \property QAbstractOAuth2::scope
105 \brief This property holds the desired scope which defines the
106 permissions requested by the client.
107
108 The scope value is updated to the scope value granted by the
109 authorization server. In case of an empty scope response, the
110 \l {https://datatracker.ietf.org/doc/html/rfc6749#section-5.1}
111 {requested scope is assumed as granted and does not change}.
112*/
113
114/*!
115 \property QAbstractOAuth2::userAgent
116 This property holds the User-Agent header used to create the
117 network requests.
118
119 The default value is "QtOAuth/1.0 (+https://www.qt.io)".
120*/
121
122/*!
123 \property QAbstractOAuth2::clientIdentifierSharedKey
124 This property holds the client shared key used as a password if
125 the server requires authentication to request the token.
126*/
127
128/*!
129 \property QAbstractOAuth2::state
130 This property holds the string sent to the server during
131 authentication. The state is used to identify and validate the
132 request when the callback is received.
133*/
134
135/*!
136 \property QAbstractOAuth2::expiration
137 This property holds the expiration time of the current access
138 token. An invalid value means that the authorization server hasn't
139 provided a valid expiration time.
140
141 \sa QDateTime::isValid()
142*/
143
144/*!
145 \fn QAbstractOAuth2::error(const QString &error, const QString &errorDescription, const QUrl &uri)
146
147 Signal emitted when the server responds to the authorization request with
148 an error as defined in \l {https://www.rfc-editor.org/rfc/rfc6749#section-5.2}
149 {RFC 6749 error response}.
150
151 \a error is the name of the error; \a errorDescription describes the error
152 and \a uri is an optional URI containing more information about the error.
153
154 \sa QAbstractOAuth::requestFailed()
155*/
156
157/*!
158 \fn QAbstractOAuth2::authorizationCallbackReceived(const QVariantMap &data)
159
160 Signal emitted when the reply server receives the authorization
161 callback from the server: \a data contains the values received
162 from the server.
163*/
164
165using OAuth2 = QAbstractOAuth2Private::OAuth2KeyString;
166const QString OAuth2::accessToken = u"access_token"_s;
167const QString OAuth2::apiKey = u"api_key"_s;
168const QString OAuth2::clientIdentifier = u"client_id"_s;
169const QString OAuth2::clientSharedSecret = u"client_secret"_s;
170const QString OAuth2::code = u"code"_s;
171const QString OAuth2::error = u"error"_s;
172const QString OAuth2::errorDescription = u"error_description"_s;
173const QString OAuth2::errorUri = u"error_uri"_s;
174const QString OAuth2::expiresIn = u"expires_in"_s;
175const QString OAuth2::grantType = u"grant_type"_s;
176const QString OAuth2::redirectUri = u"redirect_uri"_s;
177const QString OAuth2::refreshToken = u"refresh_token"_s;
178const QString OAuth2::responseType = u"response_type"_s;
179const QString OAuth2::scope = u"scope"_s;
180const QString OAuth2::state = u"state"_s;
181const QString OAuth2::tokenType = u"token_type"_s;
182const QString OAuth2::codeVerifier = u"code_verifier"_s;
183const QString OAuth2::codeChallenge = u"code_challenge"_s;
184const QString OAuth2::codeChallengeMethod = u"code_challenge_method"_s;
185
186QAbstractOAuth2Private::QAbstractOAuth2Private(const std::pair<QString, QString> &clientCredentials,
187 const QUrl &authorizationUrl,
188 QNetworkAccessManager *manager) :
189 QAbstractOAuthPrivate("qt.networkauth.oauth2",
190 authorizationUrl,
191 clientCredentials.first,
192 manager),
193 clientIdentifierSharedKey(clientCredentials.second)
194{}
195
196QAbstractOAuth2Private::~QAbstractOAuth2Private()
197{}
198
199void QAbstractOAuth2Private::setExpiresAt(const QDateTime &expiration)
200{
201 Q_ASSERT(!expiration.isValid() || expiration.timeSpec() == Qt::TimeSpec::UTC);
202 if (expiresAtUtc == expiration)
203 return;
204 Q_Q(QAbstractOAuth2);
205 expiresAtUtc = expiration;
206 emit q->expirationAtChanged(expiration: expiresAtUtc.toLocalTime());
207}
208
209QString QAbstractOAuth2Private::generateRandomState()
210{
211 return QString::fromLatin1(ba: QAbstractOAuthPrivate::generateRandomBase64String(length: 8));
212}
213
214QNetworkRequest QAbstractOAuth2Private::createRequest(QUrl url, const QVariantMap *parameters)
215{
216 QUrlQuery query(url.query());
217
218 QNetworkRequest request;
219 if (parameters) {
220 for (auto it = parameters->begin(), end = parameters->end(); it != end; ++it)
221 query.addQueryItem(key: it.key(), value: it.value().toString());
222 url.setQuery(query);
223 } else { // POST, PUT request
224 addContentTypeHeaders(request: &request);
225 }
226
227 request.setUrl(url);
228 request.setHeader(header: QNetworkRequest::UserAgentHeader, value: userAgent);
229 const QString bearer = bearerFormat.arg(a: token);
230 request.setRawHeader(headerName: "Authorization", value: bearer.toUtf8());
231 return request;
232}
233
234/*!
235 \reimp
236*/
237void QAbstractOAuth2::prepareRequest(QNetworkRequest *request, const QByteArray &verb,
238 const QByteArray &body)
239{
240 Q_D(QAbstractOAuth2);
241 Q_UNUSED(verb);
242 Q_UNUSED(body);
243 request->setHeader(header: QNetworkRequest::UserAgentHeader, value: d->userAgent);
244 const QString bearer = d->bearerFormat.arg(a: d->token);
245 request->setRawHeader(headerName: "Authorization", value: bearer.toUtf8());
246}
247
248/*!
249 Constructs a QAbstractOAuth2 object using \a parent as parent.
250*/
251QAbstractOAuth2::QAbstractOAuth2(QObject *parent) :
252 QAbstractOAuth2(nullptr, parent)
253{}
254
255/*!
256 Constructs a QAbstractOAuth2 object using \a parent as parent and
257 sets \a manager as the network access manager.
258*/
259QAbstractOAuth2::QAbstractOAuth2(QNetworkAccessManager *manager, QObject *parent) :
260 QAbstractOAuth(*new QAbstractOAuth2Private(std::make_pair(x: QString(), y: QString()),
261 QUrl(),
262 manager),
263 parent)
264{}
265
266QAbstractOAuth2::QAbstractOAuth2(QAbstractOAuth2Private &dd, QObject *parent) :
267 QAbstractOAuth(dd, parent)
268{}
269
270void QAbstractOAuth2::setResponseType(const QString &responseType)
271{
272 Q_D(QAbstractOAuth2);
273 if (d->responseType != responseType) {
274 d->responseType = responseType;
275 Q_EMIT responseTypeChanged(responseType);
276 }
277}
278
279/*!
280 Destroys the QAbstractOAuth2 instance.
281*/
282QAbstractOAuth2::~QAbstractOAuth2()
283{}
284
285/*!
286 The returned URL is based on \a url, combining it with the given
287 \a parameters and the access token.
288*/
289QUrl QAbstractOAuth2::createAuthenticatedUrl(const QUrl &url, const QVariantMap &parameters)
290{
291 Q_D(const QAbstractOAuth2);
292 if (Q_UNLIKELY(d->token.isEmpty())) {
293 qCWarning(d->loggingCategory, "Empty access token");
294 return QUrl();
295 }
296 QUrl ret = url;
297 QUrlQuery query(ret.query());
298 query.addQueryItem(key: OAuth2::accessToken, value: d->token);
299 for (auto it = parameters.begin(), end = parameters.end(); it != end ;++it)
300 query.addQueryItem(key: it.key(), value: it.value().toString());
301 ret.setQuery(query);
302 return ret;
303}
304
305/*!
306 \deprecated [6.11] Please use QtNetwork classes directly instead, see
307 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
308
309 Sends an authenticated HEAD request and returns a new
310 QNetworkReply. The \a url and \a parameters are used to create
311 the request.
312
313 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.4}
314 {Hypertext Transfer Protocol -- HTTP/1.1: HEAD}
315*/
316QNetworkReply *QAbstractOAuth2::head(const QUrl &url, const QVariantMap &parameters)
317{
318 Q_D(QAbstractOAuth2);
319 QNetworkReply *reply = d->networkAccessManager()->head(request: d->createRequest(url, parameters: &parameters));
320 connect(sender: reply, signal: &QNetworkReply::finished, context: this, slot: [this, reply]() { emit finished(reply); });
321 return reply;
322}
323
324/*!
325 \deprecated [6.11] Please use QtNetwork classes directly instead, see
326 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
327
328 Sends an authenticated GET request and returns a new
329 QNetworkReply. The \a url and \a parameters are used to create
330 the request.
331
332 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.3}
333 {Hypertext Transfer Protocol -- HTTP/1.1: GET}
334*/
335QNetworkReply *QAbstractOAuth2::get(const QUrl &url, const QVariantMap &parameters)
336{
337 Q_D(QAbstractOAuth2);
338 QNetworkReply *reply = d->networkAccessManager()->get(request: d->createRequest(url, parameters: &parameters));
339 connect(sender: reply, signal: &QNetworkReply::finished, context: this, slot: [this, reply]() { emit finished(reply); });
340 return reply;
341}
342
343/*!
344 \deprecated [6.11] Please use QtNetwork classes directly instead, see
345 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
346
347 Sends an authenticated POST request and returns a new
348 QNetworkReply. The \a url and \a parameters are used to create
349 the request.
350
351 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.5}
352 {Hypertext Transfer Protocol -- HTTP/1.1: POST}
353*/
354QNetworkReply *QAbstractOAuth2::post(const QUrl &url, const QVariantMap &parameters)
355{
356 Q_D(QAbstractOAuth2);
357 const auto data = d->convertParameters(parameters);
358 QT_IGNORE_DEPRECATIONS(return post(url, data);)
359}
360
361/*!
362 \deprecated [6.11] Please use QtNetwork classes directly instead, see
363 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
364
365 \since 5.10
366
367 \overload
368
369 Sends an authenticated POST request and returns a new
370 QNetworkReply. The \a url and \a data are used to create
371 the request.
372
373 \sa post(), {https://tools.ietf.org/html/rfc2616#section-9.6}
374 {Hypertext Transfer Protocol -- HTTP/1.1: POST}
375*/
376QNetworkReply *QAbstractOAuth2::post(const QUrl &url, const QByteArray &data)
377{
378 Q_D(QAbstractOAuth2);
379 QNetworkReply *reply = d->networkAccessManager()->post(request: d->createRequest(url), data);
380 connect(sender: reply, signal: &QNetworkReply::finished, context: this, slot: [this, reply]() { emit finished(reply); });
381 return reply;
382}
383
384/*!
385 \deprecated [6.11] Please use QtNetwork classes directly instead, see
386 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
387
388 \since 5.10
389
390 \overload
391
392 Sends an authenticated POST request and returns a new
393 QNetworkReply. The \a url and \a multiPart are used to create
394 the request.
395
396 \sa post(), QHttpMultiPart, {https://tools.ietf.org/html/rfc2616#section-9.6}
397 {Hypertext Transfer Protocol -- HTTP/1.1: POST}
398*/
399QNetworkReply *QAbstractOAuth2::post(const QUrl &url, QHttpMultiPart *multiPart)
400{
401 Q_D(QAbstractOAuth2);
402 QNetworkReply *reply = d->networkAccessManager()->post(request: d->createRequest(url), multiPart);
403 connect(sender: reply, signal: &QNetworkReply::finished, context: this, slot: [this, reply]() { emit finished(reply); });
404 return reply;
405}
406
407/*!
408 \deprecated [6.11] Please use QtNetwork classes directly instead, see
409 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
410
411 Sends an authenticated PUT request and returns a new
412 QNetworkReply. The \a url and \a parameters are used to create
413 the request.
414
415 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.6}
416 {Hypertext Transfer Protocol -- HTTP/1.1: PUT}
417*/
418QNetworkReply *QAbstractOAuth2::put(const QUrl &url, const QVariantMap &parameters)
419{
420 Q_D(QAbstractOAuth2);
421 const auto data = d->convertParameters(parameters);
422 QT_IGNORE_DEPRECATIONS(return put(url, data);)
423}
424
425/*!
426 \deprecated [6.11] Please use QtNetwork classes directly instead, see
427 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
428
429 \since 5.10
430
431 \overload
432
433 Sends an authenticated PUT request and returns a new
434 QNetworkReply. The \a url and \a data are used to create
435 the request.
436
437 \sa put(), {https://tools.ietf.org/html/rfc2616#section-9.6}
438 {Hypertext Transfer Protocol -- HTTP/1.1: PUT}
439*/
440QNetworkReply *QAbstractOAuth2::put(const QUrl &url, const QByteArray &data)
441{
442 Q_D(QAbstractOAuth2);
443 QNetworkReply *reply = d->networkAccessManager()->put(request: d->createRequest(url), data);
444 connect(sender: reply, signal: &QNetworkReply::finished, context: this, slot: std::bind(f: &QAbstractOAuth::finished, args: this, args&: reply));
445 return reply;
446}
447
448/*!
449 \deprecated [6.11] Please use QtNetwork classes directly instead, see
450 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
451
452 \since 5.10
453
454 \overload
455
456 Sends an authenticated PUT request and returns a new
457 QNetworkReply. The \a url and \a multiPart are used to create
458 the request.
459
460 \sa put(), QHttpMultiPart, {https://tools.ietf.org/html/rfc2616#section-9.6}
461 {Hypertext Transfer Protocol -- HTTP/1.1: PUT}
462*/
463QNetworkReply *QAbstractOAuth2::put(const QUrl &url, QHttpMultiPart *multiPart)
464{
465 Q_D(QAbstractOAuth2);
466 QNetworkReply *reply = d->networkAccessManager()->put(request: d->createRequest(url), multiPart);
467 connect(sender: reply, signal: &QNetworkReply::finished, context: this, slot: std::bind(f: &QAbstractOAuth::finished, args: this, args&: reply));
468 return reply;
469}
470
471/*!
472 \deprecated [6.11] Please use QtNetwork classes directly instead, see
473 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
474
475 Sends an authenticated DELETE request and returns a new
476 QNetworkReply. The \a url and \a parameters are used to create
477 the request.
478
479 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.7}
480 {Hypertext Transfer Protocol -- HTTP/1.1: DELETE}
481*/
482QNetworkReply *QAbstractOAuth2::deleteResource(const QUrl &url, const QVariantMap &parameters)
483{
484 Q_D(QAbstractOAuth2);
485 QNetworkReply *reply = d->networkAccessManager()->deleteResource(
486 request: d->createRequest(url, parameters: &parameters));
487 connect(sender: reply, signal: &QNetworkReply::finished, context: this, slot: [this, reply]() { emit finished(reply); });
488 return reply;
489}
490
491QString QAbstractOAuth2::scope() const
492{
493 Q_D(const QAbstractOAuth2);
494 return d->scope;
495}
496
497void QAbstractOAuth2::setScope(const QString &scope)
498{
499 Q_D(QAbstractOAuth2);
500 if (d->scope != scope) {
501 d->scope = scope;
502 Q_EMIT scopeChanged(scope);
503 }
504}
505
506QString QAbstractOAuth2::userAgent() const
507{
508 Q_D(const QAbstractOAuth2);
509 return d->userAgent;
510}
511
512void QAbstractOAuth2::setUserAgent(const QString &userAgent)
513{
514 Q_D(QAbstractOAuth2);
515 if (d->userAgent != userAgent) {
516 d->userAgent = userAgent;
517 Q_EMIT userAgentChanged(userAgent);
518 }
519}
520
521/*!
522 Returns the \l {https://tools.ietf.org/html/rfc6749#section-3.1.1}
523 {response_type} used.
524*/
525QString QAbstractOAuth2::responseType() const
526{
527 Q_D(const QAbstractOAuth2);
528 return d->responseType;
529}
530
531QString QAbstractOAuth2::clientIdentifierSharedKey() const
532{
533 Q_D(const QAbstractOAuth2);
534 return d->clientIdentifierSharedKey;
535}
536
537void QAbstractOAuth2::setClientIdentifierSharedKey(const QString &clientIdentifierSharedKey)
538{
539 Q_D(QAbstractOAuth2);
540 if (d->clientIdentifierSharedKey != clientIdentifierSharedKey) {
541 d->clientIdentifierSharedKey = clientIdentifierSharedKey;
542 Q_EMIT clientIdentifierSharedKeyChanged(clientIdentifierSharedKey);
543 }
544}
545
546QString QAbstractOAuth2::state() const
547{
548 Q_D(const QAbstractOAuth2);
549 return d->state;
550}
551
552void QAbstractOAuth2::setState(const QString &state)
553{
554 Q_D(QAbstractOAuth2);
555 if (state != d->state) {
556 d->state = state;
557 Q_EMIT stateChanged(state);
558 }
559}
560
561QDateTime QAbstractOAuth2::expirationAt() const
562{
563 Q_D(const QAbstractOAuth2);
564 return d->expiresAtUtc.toLocalTime();
565}
566
567/*!
568 \brief Gets the current refresh token.
569
570 Refresh tokens usually have longer lifespans than access tokens,
571 so it makes sense to save them for later use.
572
573 Returns the current refresh token or an empty string, if
574 there is no refresh token available.
575*/
576QString QAbstractOAuth2::refreshToken() const
577{
578 Q_D(const QAbstractOAuth2);
579 return d->refreshToken;
580}
581
582/*!
583 \brief Sets the new refresh token \a refreshToken to be used.
584
585 A custom refresh token can be used to refresh the access token via this method and then
586 the access token can be refreshed via QOAuth2AuthorizationCodeFlow::refreshAccessToken().
587
588*/
589void QAbstractOAuth2::setRefreshToken(const QString &refreshToken)
590{
591 Q_D(QAbstractOAuth2);
592 if (d->refreshToken != refreshToken) {
593 d->refreshToken = refreshToken;
594 Q_EMIT refreshTokenChanged(refreshToken);
595 }
596}
597
598#ifndef QT_NO_SSL
599/*!
600 \since 6.5
601
602 Returns the TLS configuration to be used when establishing a mutual TLS
603 connection between the client and the Authorization Server.
604
605 \sa setSslConfiguration(), sslConfigurationChanged()
606*/
607QSslConfiguration QAbstractOAuth2::sslConfiguration() const
608{
609 Q_D(const QAbstractOAuth2);
610 return d->sslConfiguration.value_or(u: QSslConfiguration());
611}
612
613/*!
614 \since 6.5
615
616 Sets the TLS \a configuration to be used when establishing
617 a mutual TLS connection between the client and the Authorization Server.
618
619 \sa sslConfiguration(), sslConfigurationChanged()
620*/
621void QAbstractOAuth2::setSslConfiguration(const QSslConfiguration &configuration)
622{
623 Q_D(QAbstractOAuth2);
624 const bool configChanged = !d->sslConfiguration || (*d->sslConfiguration != configuration);
625 if (configChanged) {
626 d->sslConfiguration = configuration;
627 Q_EMIT sslConfigurationChanged(configuration);
628 }
629}
630
631/*!
632 \fn void QAbstractOAuth2::sslConfigurationChanged(const QSslConfiguration &configuration)
633 \since 6.5
634
635 The signal is emitted when the TLS configuration has changed.
636 The \a configuration parameter contains the new TLS configuration.
637
638 \sa sslConfiguration(), setSslConfiguration()
639*/
640#endif // !QT_NO_SSL
641
642QT_END_NAMESPACE
643
644#include "moc_qabstractoauth2.cpp"
645

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