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 <qabstractoauth2.h>
6#include <private/qabstractoauth2_p.h>
7
8#include <QtCore/qurl.h>
9#include <QtCore/qurlquery.h>
10#include <QtCore/qbytearray.h>
11#include <QtCore/qloggingcategory.h>
12#include <QtCore/qmessageauthenticationcode.h>
13
14#include <QtNetwork/qnetworkreply.h>
15#include <QtNetwork/qnetworkrequest.h>
16#include <QtNetwork/qnetworkaccessmanager.h>
17#include <QtNetwork/qhttpmultipart.h>
18
19#ifndef QT_NO_SSL
20#include <QtNetwork/qsslconfiguration.h>
21#endif
22
23QT_BEGIN_NAMESPACE
24
25Q_STATIC_LOGGING_CATEGORY(lcOAuth2Validation, "qt.networkauth.oauth2.validation")
26
27using namespace Qt::StringLiterals;
28using namespace std::chrono_literals;
29
30static constexpr auto MinimumRefreshLeadTime = 10s;
31static constexpr auto FallbackRefreshInterval = 2s;
32
33/*!
34 \class QAbstractOAuth2
35 \inmodule QtNetworkAuth
36 \ingroup oauth
37 \brief The QAbstractOAuth2 class is the base of all
38 implementations of OAuth 2 authentication methods.
39 \since 5.8
40
41 The class defines the basic interface of the OAuth 2
42 authentication classes. By inheriting this class, you
43 can create custom authentication methods using the OAuth 2
44 standard for different web services.
45
46 A description of how OAuth 2 works can be found in:
47 \l {https://tools.ietf.org/html/rfc6749}{The OAuth 2.0
48 Authorization Framework}
49*/
50
51/*!
52 \page oauth-http-method-alternatives
53 \title OAuth2 HTTP method alternatives
54 \brief This page provides alternatives for QtNetworkAuth
55 OAuth2 HTTP methods.
56
57 QtNetworkAuth provides HTTP Methods such as \l {QAbstractOAuth::get()}
58 for issuing authenticated requests. In the case of OAuth2,
59 this typically means setting the
60 \l {QHttpHeaders::WellKnownHeader}{Authorization} header, as
61 specified in \l {https://datatracker.ietf.org/doc/html/rfc6750#section-2.1}
62 {RFC 6750}.
63
64 Since this operation is straightforward to do, it is better to use
65 the normal QtNetwork HTTP method APIs directly, and set this header
66 manually. These QtNetwork APIs have less assumptions on the message
67 content types and provide a broader set of APIs.
68
69 See \l QRestAccessManager, \l QNetworkAccessManager, QNetworkRequest,
70 QNetworkRequestFactory.
71
72 \section1 QNetworkRequest
73
74 The needed \e Authorization header can be set directly on each
75 request needing authorization.
76
77 \code
78 using namespace Qt::StringLiterals;
79
80 QOAuth2AuthorizationCodeFlow m_oauth;
81 QNetworkRequest request;
82
83 QHttpHeaders headers;
84 headers.append(QHttpHeaders::WellKnownHeader::Authorization, u"Bearer "_s + m_oauth.token());
85 request.setHeaders(headers);
86 \endcode
87
88 After setting the header, use the request normally with either
89 \l QRestAccessManager or \l QNetworkAccessManager.
90
91 \section1 QNetworkRequestFactory
92
93 QNetworkRequestFactory is a convenience class introduced in Qt 6.7.
94 It provides a suitable method for this task:
95 \l {QNetworkRequestFactory::setBearerToken()}, as illustrated
96 by the code below.
97
98 \code
99 QNetworkRequestFactory m_api({"https://www.example.com/v3"});
100 QOAuth2AuthorizationCodeFlow m_oauth;
101 // ...
102 connect(&m_oauth, &QOAuth2AuthorizationCodeFlow::granted, this, [this]{
103 m_api.setBearerToken(m_oauth.token().toLatin1());
104 });
105 \endcode
106
107 After setting the bearer token, use the request factory normally
108 with either \l QRestAccessManager or \l QNetworkAccessManager.
109*/
110
111#ifndef QOAUTH2_NO_LEGACY_SCOPE
112/*!
113 \deprecated [6.13] Use requestedScopeTokens and grantedScopeTokens
114 properties instead. This property will be removed in Qt 7.
115 \property QAbstractOAuth2::scope
116 \brief This property holds the desired scope which defines the
117 permissions requested by the client.
118
119 The scope value is updated to the scope value granted by the
120 authorization server. In case of an empty scope response, the
121 \l {https://datatracker.ietf.org/doc/html/rfc6749#section-5.1}
122 {requested scope is assumed as granted and does not change}.
123
124 The fact that this property serves two different roles, first
125 as the requested scope and later as the granted scope, is an historical
126 artefact. All new code is recommended to use
127 \l QAbstractOAuth2::requestedScopeTokens and
128 \l QAbstractOAuth2::grantedScopeTokens.
129
130 \sa QAbstractOAuth2::grantedScopeTokens,
131 QAbstractOAuth2::requestedScopeTokens
132*/
133#endif
134
135/*!
136 \since 6.9
137 \property QAbstractOAuth2::grantedScopeTokens
138 \brief This property holds the scope granted by the authorization
139 server.
140
141 The requested and granted scope may differ. End-user may have opted
142 to grant only a subset of the scope, or server-side policies may
143 change it. The application should be prepared to handle this
144 scenario, and check the granted scope to see if it should impact
145 the application logic.
146
147 The server may omit indicating the granted scope altogether, as defined by
148 \l {https://datatracker.ietf.org/doc/html/rfc6749#section-5.1}{RFC 6749}.
149 In this case the implementation assumes the granted scope is the same as
150 the requested scope.
151
152 \sa QAbstractOAuth2::requestedScopeTokens
153*/
154
155/*!
156 \since 6.9
157 \property QAbstractOAuth2::requestedScopeTokens
158 \brief This property holds the desired scope which defines the
159 permissions requested by the client.
160
161 \note Scope tokens are limited to a
162 \l {https://datatracker.ietf.org/doc/html/rfc6749#section-3.3}{subset}
163 of printable US-ASCII characters. Using characters outside this range
164 is not supported.
165
166 \sa QAbstractOAuth2::grantedScopeTokens
167*/
168
169/*!
170 \since 6.9
171 \property QAbstractOAuth2::nonceMode
172 \brief This property holds the current nonce mode (whether or not
173 nonce is used).
174
175 \sa NonceMode, nonce
176*/
177
178/*!
179 \since 6.9
180 \enum QAbstractOAuth2::NonceMode
181
182 List of available
183 \l {https://openid.net/specs/openid-connect-core-1_0-final.html#IDToken}{nonce}
184 modes.
185
186 \value Automatic Nonce is sent if the
187 \l {requestedScopeTokens}{requested scope} contains \c {openid}.
188 This is the default mode, and sends \c {nonce} only when it's
189 relevant to OIDC authentication flows.
190 \value Enabled Nonce is sent during authorization stage.
191 \value Disabled Nonce is not sent during authorization stage.
192
193 \sa nonce, {OAuth 2.0 Overview}
194*/
195
196/*!
197 \since 6.9
198 \property QAbstractOAuth2::nonce
199
200 This property holds the string sent to the server during
201 authentication. The nonce is used to associate applicable
202 token responses (OpenID Connect \c {id_token} in particular)
203 with the authorization stage.
204
205 The primary purpose of the \c {nonce} is to mitigate replay attacks.
206 It ensures that the token responses received are in response
207 to the authentication requests initiated by the application,
208 preventing attackers from reusing tokens in unauthorized contexts.
209 Therefore, it's important to include nonce verification as part of
210 the token validation.
211
212 In practice, authorization server vendors may refuse the OpenID Connect
213 request if \l {https://openid.net/specs/openid-connect-core-1_0-final.html#AuthRequest}
214 {a nonce isn't provided in the Authorization request}.
215
216 The token itself is an opaque string, and should contain only
217 URL-safe characters for maximum compatibility. Further the
218 token must provide adequate entropy
219 \l {https://openid.net/specs/openid-connect-core-1_0-final.html#NonceNotes}
220 {so that it's unguessable to attackers}. There are no strict size
221 limits for nonce, and authorization server vendors may impose their own
222 minimum and maximum sizes.
223
224 While the \c {nonce} can be set manually, Qt classes will
225 generate a 32-character nonce \l {NonceMode}{when needed} if
226 one isn't set.
227
228 \sa nonceMode, {Qt OpenID Connect Support}
229*/
230
231/*!
232 \since 6.9
233 \property QAbstractOAuth2::idToken
234
235 This property holds the received
236 \l {https://openid.net/specs/openid-connect-core-1_0-final.html#CodeIDToken}
237 {OpenID Connect ID token}.
238
239 \sa NonceMode, nonce, {Qt OpenID Connect Support}
240*/
241
242/*!
243 \fn template<typename Functor, QAbstractOAuth2::if_compatible_callback<Functor>> void QAbstractOAuth2::setNetworkRequestModifier(
244 const ContextTypeForFunctor<Functor> *context,
245 Functor &&callback)
246 \since 6.9
247
248 Sets the network request modification function to \a callback.
249 This function is used to customize the network requests sent
250 to the server.
251
252 \a callback has to implement the signature
253 \c {void(QNetworkRequest&, QAbstractOAuth::Stage)}. The provided
254 QNetworkRequest can be directly modified, and it is used right after
255 the callback finishes. \a callback can be a function pointer, lambda,
256 member function, or any callable object. The provided
257 QAbstractOAuth::Stage can be used to check to which stage
258 the request relates to (token request, token refresh request,
259 or authorization request in case of QOAuth2DeviceAuthorizationFlow).
260
261 \a context controls the lifetime of the calls, and prevents
262 access to de-allocated resources in case \a context is destroyed.
263 In other words, if the object provided as context is destroyed,
264 callbacks won't be executed. \a context must point to a valid
265 QObject (and in case the callback is a member function,
266 it needs to actually have it). Since the callback's results
267 are used immediately, \a context must reside in the same
268 thread as the QAbstractOAuth2 instance.
269
270 \sa clearNetworkRequestModifier(), QNetworkRequest
271*/
272
273/*!
274 \property QAbstractOAuth2::userAgent
275 This property holds the User-Agent header used to create the
276 network requests.
277
278 The default value is "QtOAuth/1.0 (+https://www.qt.io)".
279*/
280
281/*!
282 \property QAbstractOAuth2::clientIdentifierSharedKey
283 This property holds the client shared key used as a password if
284 the server requires authentication to request the token.
285*/
286
287/*!
288 \property QAbstractOAuth2::state
289 This property holds the string sent to the server during
290 authentication. The state is used to identify and validate the
291 request when the callback is received.
292
293 Certain characters are illegal in the state element (see
294 \l {https://datatracker.ietf.org/doc/html/rfc6749#appendix-A.5}{RFC 6749}).
295 The use of illegal characters could lead to an unintended state mismatch
296 and a failing OAuth 2 authorization. Therefore, if you attempt to set
297 a value that contains illegal characters, the state is ignored and a
298 warning is logged.
299*/
300
301/*!
302 \property QAbstractOAuth2::expiration
303 This property holds the expiration time of the current access
304 token. An invalid value means that the authorization server hasn't
305 provided a valid expiration time.
306
307 \sa QDateTime::isValid()
308*/
309
310/*!
311 \fn QAbstractOAuth2::accessTokenAboutToExpire()
312 \since 6.9
313 \brief The signal is emitted when the access token is about
314 to expire.
315
316 Emitting this signal requires that the access token has
317 a valid expiration time. An alternative for handling this
318 signal manually is to use \l {autoRefresh}.
319
320 \sa refreshLeadTime, autoRefresh, refreshTokens()
321*/
322
323/*!
324 \property QAbstractOAuth2::refreshLeadTime
325 \since 6.9
326 \brief This property defines how far in advance
327 \l {accessTokenAboutToExpire()} signal is emitted relative
328 to the access token's expiration.
329
330 This property specifies the time interval (in seconds)
331 before the current access token’s expiration, when
332 \l {accessTokenAboutToExpire()} signal is emitted. The value
333 set for this property must be a positive duration.
334
335 This interval allows the application to refresh the token well
336 in advance, ensuring continuous authorization without interruptions.
337
338 If this property is not explicitly set, or the provided leadTime is
339 larger than the token's lifetime, the leadTime defaults to
340 5% of the token's remaining lifetime, but not less than 10 seconds ahead
341 of expiration (leaving time for the refresh request to complete).
342
343 \note Expiration signal only works if the authorization server has provided
344 a proper expiration time.
345
346 \sa autoRefresh
347*/
348
349/*!
350 \property QAbstractOAuth2::autoRefresh
351 \since 6.9
352 \brief This property enables or disables automatic refresh of
353 the access token.
354
355 This property enables or disables the automatic refresh of the
356 access token. This is useful for applications that require uninterrupted
357 authorization without user intervention.
358
359 If this property is \c true, \l refreshTokens()
360 will be automatically called when the token is about to expire and
361 a valid \l refreshToken exists.
362
363 \sa refreshLeadTime, accessTokenAboutToExpire()
364*/
365
366/*!
367 \property QAbstractOAuth2::tokenUrl
368 \since 6.9
369
370 This property holds the token endpoint URL which is used to obtain tokens.
371 Depending on the use case and authorization server support, these tokens
372 can be access tokens, refresh tokens, and ID tokens.
373
374 Tokens are typically retrieved once the authorization stage is completed,
375 and the token endpoint can also be used to refresh tokens as needed.
376
377 For example, \l QOAuth2AuthorizationCodeFlow uses this url to issue
378 \l {https://tools.ietf.org/html/rfc6749#section-4.1.3}
379 {an access token request},
380 and \l QOAuth2DeviceAuthorizationFlow uses this url
381 \l {https://datatracker.ietf.org/doc/html/rfc8628#section-3.4}
382 {to poll for an access token}.
383*/
384
385/*!
386 \deprecated [6.13] Use serverReportedErrorOccurred instead
387 \fn QAbstractOAuth2::error(const QString &error, const QString &errorDescription, const QUrl &uri)
388
389 Signal emitted when the server responds to the authorization request with
390 an error as defined in \l {https://www.rfc-editor.org/rfc/rfc6749#section-5.2}
391 {RFC 6749 error response}.
392
393 \a error is the name of the error; \a errorDescription describes the error
394 and \a uri is an optional URI containing more information about the error.
395
396 \sa QAbstractOAuth::requestFailed()
397 \sa QAbstractOAuth2::serverReportedErrorOccurred()
398*/
399
400/*!
401 \fn QAbstractOAuth2::serverReportedErrorOccurred(const QString &error,
402 const QString &errorDescription,
403 const QUrl &uri)
404 \since 6.9
405
406 Signal emitted when the server responds to the authorization request with
407 an error as defined in \l {https://www.rfc-editor.org/rfc/rfc6749#section-5.2}
408 {RFC 6749 error response}.
409
410 \a error is the name of the error; \a errorDescription describes the error
411 and \a uri is an optional URI containing more information about the error.
412
413 To catch all errors, including these RFC defined errors, with a
414 single signal, use \l {QAbstractOAuth::requestFailed()}.
415*/
416
417/*!
418 \fn QAbstractOAuth2::authorizationCallbackReceived(const QVariantMap &data)
419
420 Signal emitted when the reply server receives the authorization
421 callback from the server: \a data contains the values received
422 from the server.
423*/
424
425QAbstractOAuth2Private::QAbstractOAuth2Private(const std::pair<QString, QString> &clientCredentials,
426 const QUrl &authorizationUrl,
427 QNetworkAccessManager *manager) :
428 QAbstractOAuthPrivate("qt.networkauth.oauth2",
429 authorizationUrl,
430 clientCredentials.first,
431 manager),
432 clientIdentifierSharedKey(clientCredentials.second)
433{}
434
435QAbstractOAuth2Private::~QAbstractOAuth2Private()
436{}
437
438void QAbstractOAuth2Private::setExpiresAt(const QDateTime &expiration)
439{
440 Q_ASSERT(!expiration.isValid() || expiration.timeSpec() == Qt::TimeSpec::UTC);
441 if (expiresAtUtc == expiration)
442 return;
443 Q_Q(QAbstractOAuth2);
444 expiresAtUtc = expiration;
445 emit q->expirationAtChanged(expiration: expiresAtUtc.toLocalTime());
446}
447
448void QAbstractOAuth2Private::setGrantedScopeTokens(const QSet<QByteArray> &tokens)
449{
450 if (tokens == grantedScopeTokens)
451 return;
452 Q_Q(QAbstractOAuth2);
453 grantedScopeTokens = tokens;
454 Q_EMIT q->grantedScopeTokensChanged(tokens: grantedScopeTokens);
455}
456
457QString QAbstractOAuth2Private::joinedScope(const QSet<QByteArray> &scopeTokens)
458{
459 QString result;
460 auto separator = ""_L1;
461 for (const auto &token : scopeTokens) {
462 result += separator;
463 result += QLatin1StringView{token};
464 separator = " "_L1;
465 }
466 return result;
467}
468
469QSet<QByteArray> QAbstractOAuth2Private::splitScope(QStringView scope)
470{
471 QSet<QByteArray> result;
472 for (auto token : scope.tokenize(needle: u' ', flags: Qt::SkipEmptyParts)) {
473 warnOnInvalidScopeToken(token);
474 result.insert(value: token.toUtf8());
475 }
476 return result;
477}
478
479constexpr auto is_invalid_scope_token_char = [](char16_t ch) noexcept
480{
481 // https://datatracker.ietf.org/doc/html/rfc6749#section-3.3
482 // scope-token = 1*( %x21 / %x23-5B / %x5D-7E )
483 // ie. all US-ASCII, except control, SP, DQUOTE, and BACKSLASH
484 return ch < 0x21 || ch == 0x22 || ch == 0x5C || ch > 0x7E;
485};
486
487bool QAbstractOAuth2Private::checkRequestedScopeTokensValid(const QSet<QByteArray> &scopeTokens)
488{
489 return std::all_of(first: scopeTokens.begin(), last: scopeTokens.end(),
490 pred: [] (auto token) { return checkRequestedScopeTokenValid(token); });
491}
492
493/*!
494 \internal
495
496 Validates an RFC 6749 scope-token in octet-stream form, ie. before
497 serialization. Warns also if \a token is empty (since it will then vanish
498 on serialization (cause two spaces instead of one), which is probably not
499 what the user wanted.
500*/
501bool QAbstractOAuth2Private::checkRequestedScopeTokenValid(QByteArrayView token)
502{
503 if (token.isEmpty()) {
504 qCWarning(lcOAuth2Validation, "A scope token cannot be empty.");
505 return false;
506 }
507
508 // The predicate takes char16_t, but the range is of value_type char. But
509 // the signedness of `char` doesn't matter here: regardless of sign an
510 // invalid char remains an invalid char after conversion to char16_t:
511 const auto it = std::find_if(first: token.begin(), last: token.end(), pred: is_invalid_scope_token_char);
512 if (it != token.end()) {
513 qCWarning(lcOAuth2Validation,
514 "A scope token cannot contain disallowed character '%c' (0x%02x). "
515 "Note that Qt requires scope-tokens are RFC 6749-compliant US-ASCII-only. "
516 "Please continue to use the QAbstractOAuth2::scope property for the time "
517 "being, and consider filing a bug if you need this behavior.",
518 *it, uchar(*it));
519 return false;
520 }
521
522 return true;
523}
524
525/*!
526 \internal
527
528 Validates an RFC 6749 scope-token in UTF-16 encoding, ie. while splitting a
529 scope into scope-tokens. This function assumes that \a token is not empty,
530 because the cardinality of spaces separating scope-tokens is not
531 significant, so Qt::SkipEmptyParts should have been used by caller).
532*/
533void QAbstractOAuth2Private::warnOnInvalidScopeToken(QStringView token)
534{
535 if (!lcOAuth2Validation().isWarningEnabled())
536 return;
537
538 Q_ASSERT(!token.isEmpty()); // we only operate in UTF-16 space while splitting,
539 // which we do along " +", so this cannot happen.
540
541 const auto b = token.utf16(); // want char16_t, not QChar
542 const auto e = b + token.size();
543 const auto it = std::find_if(first: b, last: e, pred: is_invalid_scope_token_char);
544 if (it != e) {
545 qCWarning(lcOAuth2Validation,
546 "Scope token contains disallowed character '%s' (0x%04x). "
547 "This may cause interoperability issues",
548 qUtf8Printable(QChar(*it)), *it);
549 }
550}
551
552QString QAbstractOAuth2Private::generateRandomState()
553{
554 return QString::fromLatin1(ba: QAbstractOAuthPrivate::generateRandomBase64String(length: 8));
555}
556
557QString QAbstractOAuth2Private::generateNonce()
558{
559 // There is no strict minimum or maximum size for nonce, but
560 // generating a 32-character base64 URL string provides
561 // ~192 bits of entropy (32 characters * 6 bits per character).
562 return QString::fromLatin1(ba: QAbstractOAuthPrivate::generateRandomBase64String(length: 32));
563}
564
565QNetworkRequest QAbstractOAuth2Private::createRequest(QUrl url, const QVariantMap *parameters)
566{
567 QUrlQuery query(url.query());
568
569 QNetworkRequest request;
570 if (parameters) {
571 for (auto it = parameters->begin(), end = parameters->end(); it != end; ++it)
572 query.addQueryItem(key: it.key(), value: it.value().toString());
573 url.setQuery(query);
574 } else { // POST, PUT request
575 addContentTypeHeaders(request: &request);
576 }
577
578 request.setUrl(url);
579 request.setHeader(header: QNetworkRequest::UserAgentHeader, value: userAgent);
580 const QString bearer = bearerFormat.arg(a: token);
581 request.setRawHeader(headerName: "Authorization", value: bearer.toUtf8());
582 return request;
583}
584
585void QAbstractOAuth2Private::initializeRefreshHandling()
586{
587 Q_Q(QAbstractOAuth2);
588 refreshTimer.setSingleShot(true);
589 QObject::connect(sender: q, signal: &QAbstractOAuth2::expirationAtChanged, context: q, slot: [this]() {
590 updateRefreshTimer(clientSideUpdate: false);
591 });
592 QObject::connect(sender: &refreshTimer, signal: &QChronoTimer::timeout, context: q,
593 slot: &QAbstractOAuth2::accessTokenAboutToExpire);
594 QObject::connect(sender: q, signal: &QAbstractOAuth2::accessTokenAboutToExpire, context: q, slot: [q] {
595 if (q->autoRefresh() && !q->refreshToken().isEmpty())
596 q->refreshTokens();
597 });
598}
599
600void QAbstractOAuth2Private::updateRefreshTimer(bool clientSideUpdate)
601{
602 Q_Q(QAbstractOAuth2);
603 qCDebug(loggingCategory, "Updating refresh timer");
604
605 refreshTimer.stop();
606
607 if (!q->expirationAt().isValid()) {
608 qCDebug(loggingCategory, "Expiration time not valid");
609 return;
610 }
611
612 auto leadTime = q->refreshLeadTime();
613 std::chrono::seconds untilNextExpiration = std::chrono::seconds(
614 QDateTime::currentDateTime().secsTo(q->expirationAt()));
615
616 // If leadTime is zero, or larger than token's lifetime, estimate a decent leadTime
617 if (leadTime == 0s || leadTime.count() >= tokenLifetime) {
618 leadTime = qMax(a: MinimumRefreshLeadTime, b: untilNextExpiration / 20);
619 qCDebug(loggingCategory, "Adjusted expiration leadTime to %lld seconds",
620 static_cast<long long>(leadTime.count()));
621 }
622
623 std::chrono::seconds interval = untilNextExpiration - leadTime;
624
625 if (interval < MinimumRefreshLeadTime) {
626 if (clientSideUpdate) {
627 // Refresh timer update was triggered by the application, and the pre-existing
628 // token is near expiration => emit expiration immediately
629 qCDebug(loggingCategory, "Token expiration immediate");
630 emit q->accessTokenAboutToExpire();
631 return;
632 }
633 // Refresh update was triggered by a response from authorization server,
634 // and the token is already near expiration. Use a miminum update interval to avoid
635 // intense refresh request looping (treat as a misconfigured authorization server)
636 interval = qMax(a: interval, b: FallbackRefreshInterval);
637 }
638
639 qCDebug(loggingCategory, "Token refresh timer will expire in %lld seconds",
640 static_cast<long long>(interval.count()));
641 refreshTimer.setInterval(interval);
642 refreshTimer.start();
643}
644
645bool QAbstractOAuth2Private::authorizationShouldIncludeNonce() const
646{
647 switch (nonceMode) {
648 case QAbstractOAuth2::NonceMode::Enabled:
649 return true;
650 case QAbstractOAuth2::NonceMode::Disabled:
651 return false;
652 case QAbstractOAuth2::NonceMode::Automatic:
653 return requestedScopeTokens.contains(value: "openid");
654 };
655 return false;
656}
657
658void QAbstractOAuth2Private::setIdToken(const QString &token)
659{
660 Q_Q(QAbstractOAuth2);
661 if (idToken == token)
662 return;
663 idToken = token;
664 emit q->idTokenChanged(idToken);
665}
666
667void QAbstractOAuth2Private::_q_tokenRequestFailed(QAbstractOAuth::Error error,
668 const QString& errorString)
669{
670 Q_Q(QAbstractOAuth);
671 qCWarning(loggingCategory) << "Token request failed:" << errorString;
672 // If we were refreshing, reset status to Granted if we have an access token.
673 // The access token might still be valid, and even if it wouldn't be,
674 // refreshing can be attempted again.
675 if (q->status() == QAbstractOAuth::Status::RefreshingToken) {
676 if (!q->token().isEmpty())
677 setStatus(QAbstractOAuth::Status::Granted);
678 else
679 setStatus(QAbstractOAuth::Status::NotAuthenticated);
680 }
681 emit q->requestFailed(error);
682}
683
684void QAbstractOAuth2Private::_q_tokenRequestFinished(const QVariantMap &values)
685{
686 Q_Q(QAbstractOAuth2);
687
688 if (values.contains(key: QtOAuth2RfcKeywords::error)) {
689 _q_tokenRequestFailed(error: QAbstractOAuth::Error::ServerError,
690 errorString: values.value(key: QtOAuth2RfcKeywords::error).toString());
691 return;
692 }
693
694 bool ok;
695 const QString accessToken = values.value(key: QtOAuth2RfcKeywords::accessToken).toString();
696 tokenType = values.value(key: QtOAuth2RfcKeywords::tokenType).toString();
697 tokenLifetime = values.value(key: QtOAuth2RfcKeywords::expiresIn).toLongLong(ok: &ok);
698 if (!ok)
699 tokenLifetime = 0;
700 if (values.value(key: QtOAuth2RfcKeywords::refreshToken).isValid())
701 q->setRefreshToken(values.value(key: QtOAuth2RfcKeywords::refreshToken).toString());
702
703 if (accessToken.isEmpty()) {
704 _q_tokenRequestFailed(error: QAbstractOAuth::Error::OAuthTokenNotFoundError,
705 errorString: "Access token not received"_L1);
706 return;
707 }
708 q->setToken(accessToken);
709
710 // RFC 6749 section 5.1 https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
711 // If the requested scope and granted scopes differ, server is REQUIRED to return
712 // the scope. If OTOH the scopes match, the server MAY omit the scope in the response,
713 // in which case we assume that the granted scope matches the requested scope.
714 //
715 // Note: 'scope' variable has two roles: requested scope, and later granted scope.
716 // Therefore 'scope' needs to be set if the granted scope differs from 'scope'.
717 QString receivedGrantedScope = values.value(key: QtOAuth2RfcKeywords::scope).toString();
718 const QSet<QByteArray> splitGrantedScope = splitScope(scope: receivedGrantedScope);
719 if (splitGrantedScope.isEmpty()) {
720 setGrantedScopeTokens(requestedScopeTokens);
721 } else {
722 setGrantedScopeTokens(splitGrantedScope);
723#ifndef QOAUTH2_NO_LEGACY_SCOPE
724 if (receivedGrantedScope != legacyScope) {
725 legacyScope = std::move(receivedGrantedScope);
726 QT_IGNORE_DEPRECATIONS(Q_EMIT q->scopeChanged(legacyScope);)
727 }
728#endif
729 }
730
731 // An id_token must be included if this was an OIDC request
732 // https://openid.net/specs/openid-connect-core-1_0-final.html#AuthRequest (cf. 'scope')
733 // https://openid.net/specs/openid-connect-core-1_0-final.html#TokenResponse
734 const QString receivedIdToken = values.value(key: QtOAuth2RfcKeywords::idToken).toString();
735 if (grantedScopeTokens.contains(value: "openid") && receivedIdToken.isEmpty()) {
736 setIdToken({});
737 _q_tokenRequestFailed(error: QAbstractOAuth::Error::OAuthTokenNotFoundError,
738 errorString: "ID token not received"_L1);
739 return;
740 }
741 setIdToken(receivedIdToken);
742
743 if (tokenLifetime > 0)
744 setExpiresAt(QDateTime::currentDateTimeUtc().addSecs(secs: tokenLifetime));
745 else
746 setExpiresAt(QDateTime());
747
748 QVariantMap copy(values);
749 copy.remove(key: QtOAuth2RfcKeywords::accessToken);
750 copy.remove(key: QtOAuth2RfcKeywords::expiresIn);
751 copy.remove(key: QtOAuth2RfcKeywords::refreshToken);
752 copy.remove(key: QtOAuth2RfcKeywords::scope);
753 copy.remove(key: QtOAuth2RfcKeywords::tokenType);
754 copy.remove(key: QtOAuth2RfcKeywords::idToken);
755 QVariantMap newExtraTokens = extraTokens;
756 newExtraTokens.insert(map: copy);
757 setExtraTokens(newExtraTokens);
758
759 setStatus(QAbstractOAuth::Status::Granted);
760}
761
762bool QAbstractOAuth2Private::handleRfcErrorResponseIfPresent(const QVariantMap &data)
763{
764 Q_Q(QAbstractOAuth2);
765 const QString error = data.value(key: QtOAuth2RfcKeywords::error).toString();
766
767 if (error.size()) {
768 // RFC 6749, Section 5.2 Error Response
769 const QString uri = data.value(key: QtOAuth2RfcKeywords::errorUri).toString();
770 const QString description = data.value(key: QtOAuth2RfcKeywords::errorDescription).toString();
771 qCWarning(loggingCategory, "Authorization stage: AuthenticationError: %s(%s): %s",
772 qPrintable(error), qPrintable(uri), qPrintable(description));
773
774#if QT_DEPRECATED_SINCE(6, 13)
775 QT_IGNORE_DEPRECATIONS(Q_EMIT q->error(error, description, uri);)
776#endif
777 Q_EMIT q->serverReportedErrorOccurred(error, errorDescription: description, uri);
778
779 // Emit also requestFailed() so that it is a signal for all errors
780 emit q->requestFailed(error: QAbstractOAuth::Error::ServerError);
781 return true;
782 }
783 return false;
784}
785
786QAbstractOAuth2Private::RequestAndBody QAbstractOAuth2Private::createRefreshRequestAndBody(
787 const QUrl &url)
788{
789 RequestAndBody result;
790 result.request.setUrl(url);
791
792 QMultiMap<QString, QVariant> parameters;
793#ifndef QT_NO_SSL
794 if (sslConfiguration && !sslConfiguration->isNull())
795 result.request.setSslConfiguration(*sslConfiguration);
796#endif
797 QUrlQuery query;
798 parameters.insert(key: QtOAuth2RfcKeywords::grantType, QStringLiteral("refresh_token"));
799 parameters.insert(key: QtOAuth2RfcKeywords::refreshToken, value: refreshToken);
800 parameters.insert(key: QtOAuth2RfcKeywords::clientIdentifier, value: clientIdentifier);
801 parameters.insert(key: QtOAuth2RfcKeywords::clientSharedSecret, value: clientIdentifierSharedKey);
802 if (modifyParametersFunction)
803 modifyParametersFunction(QAbstractOAuth::Stage::RefreshingAccessToken, &parameters);
804 query = QAbstractOAuthPrivate::createQuery(parameters);
805 result.request.setHeader(header: QNetworkRequest::ContentTypeHeader,
806 QStringLiteral("application/x-www-form-urlencoded"));
807
808 callNetworkRequestModifier(request&: result.request, stage: QAbstractOAuth::Stage::RefreshingAccessToken);
809 result.body = query.toString(encoding: QUrl::FullyEncoded).toLatin1();
810
811 return result;
812}
813
814void QAbstractOAuth2Private::logAuthorizationStageWarning(QLatin1StringView message)
815{
816 static constexpr auto base = "Authorization stage: %s";
817 qCWarning(loggingCategory, base, message.latin1());
818}
819
820void QAbstractOAuth2Private::logAuthorizationStageWarning(QLatin1StringView message, int detail)
821{
822 static constexpr auto base = "Authorization stage: %s: %d";
823 qCWarning(loggingCategory, base, message.latin1(), detail);
824}
825
826void QAbstractOAuth2Private::logTokenStageWarning(QLatin1StringView message)
827{
828 static constexpr auto base = "Token stage: %s";
829 qCWarning(loggingCategory, base, message.latin1());
830}
831
832bool QAbstractOAuth2Private::verifyThreadAffinity(const QObject *contextObject)
833{
834 Q_Q(QAbstractOAuth2);
835 if (contextObject && (contextObject->thread() != q->thread())) {
836 qCWarning(loggingCategory, "Context object must reside in the same thread");
837 return false;
838 }
839 return true;
840}
841
842void QAbstractOAuth2Private::callNetworkRequestModifier(QNetworkRequest &request,
843 QAbstractOAuth::Stage stage)
844{
845 if (networkRequestModifier.contextObject && networkRequestModifier.slot) {
846 if (!verifyThreadAffinity(contextObject: networkRequestModifier.contextObject)) {
847 Q_Q(QAbstractOAuth2);
848 q->clearNetworkRequestModifier();
849 return;
850 }
851 void *argv[] = { nullptr, &request, &stage};
852 networkRequestModifier.slot->call(
853 r: const_cast<QObject*>(networkRequestModifier.contextObject.get()), a: argv);
854 }
855}
856
857/*!
858 \reimp
859*/
860void QAbstractOAuth2::prepareRequest(QNetworkRequest *request, const QByteArray &verb,
861 const QByteArray &body)
862{
863 Q_D(QAbstractOAuth2);
864 Q_UNUSED(verb);
865 Q_UNUSED(body);
866 request->setHeader(header: QNetworkRequest::UserAgentHeader, value: d->userAgent);
867 const QString bearer = d->bearerFormat.arg(a: d->token);
868 request->setRawHeader(headerName: "Authorization", value: bearer.toUtf8());
869}
870
871/*!
872 Constructs a QAbstractOAuth2 object using \a parent as parent.
873*/
874QAbstractOAuth2::QAbstractOAuth2(QObject *parent) :
875 QAbstractOAuth2(nullptr, parent)
876{}
877
878/*!
879 Constructs a QAbstractOAuth2 object using \a parent as parent and
880 sets \a manager as the network access manager.
881*/
882QAbstractOAuth2::QAbstractOAuth2(QNetworkAccessManager *manager, QObject *parent) :
883 QAbstractOAuth(*new QAbstractOAuth2Private(std::make_pair(x: QString(), y: QString()),
884 QUrl(),
885 manager),
886 parent)
887{
888 Q_D(QAbstractOAuth2);
889 d->initializeRefreshHandling();
890}
891
892QAbstractOAuth2::QAbstractOAuth2(QAbstractOAuth2Private &dd, QObject *parent) :
893 QAbstractOAuth(dd, parent)
894{
895 Q_D(QAbstractOAuth2);
896 d->initializeRefreshHandling();
897}
898
899void QAbstractOAuth2::setResponseType(const QString &responseType)
900{
901 Q_D(QAbstractOAuth2);
902 if (d->responseType != responseType) {
903 d->responseType = responseType;
904 Q_EMIT responseTypeChanged(responseType);
905 }
906}
907
908void QAbstractOAuth2::setNetworkRequestModifierImpl(const QObject* context,
909 QtPrivate::QSlotObjectBase *slot)
910{
911 Q_D(QAbstractOAuth2);
912
913 if (!context) {
914 qCWarning(d->loggingCategory, "Context object must not be null, ignoring");
915 return;
916 }
917 if (!d->verifyThreadAffinity(contextObject: context))
918 return;
919
920 d->networkRequestModifier.contextObject = context;
921 d->networkRequestModifier.slot.reset(p: slot);
922}
923
924/*!
925 Clears the network request modifier.
926
927 \sa setNetworkRequestModifier()
928*/
929void QAbstractOAuth2::clearNetworkRequestModifier()
930{
931 Q_D(QAbstractOAuth2);
932 d->networkRequestModifier = {.contextObject: nullptr, .slot: nullptr};
933}
934
935/*!
936 \since 6.9
937
938 Call this function to refresh the tokens. The function calls
939 \l {refreshTokensImplementation()} to perform the actual refresh.
940
941 \sa refreshTokensImplementation(), autoRefresh
942*/
943void QAbstractOAuth2::refreshTokens()
944{
945#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
946 // Due to binary compatibility reasons we can't add new virtuals to this baseclass,
947 // but instead we mimic the virtual by invoking the implementation as a slot
948 QMetaObject::invokeMethod(obj: this, member: "refreshTokensImplementation", c: Qt::DirectConnection);
949#else
950 refreshTokensImplementation();
951#endif
952}
953
954/*!
955 \fn void QAbstractOAuth2::refreshTokensImplementation()
956 \since 6.9
957
958 This slot is called by \l refreshTokens() to send the token
959 refresh request.
960
961\if defined(qt7)
962 The derived classes \e must reimplement this slot to support token
963 refreshing:
964\else
965 The derived classes \e should reimplement this slot to support token
966 refreshing:
967\endif
968 \snippet src_oauth_replyhandlers.cpp custom-class-def-start
969 \dots
970 \snippet src_oauth_replyhandlers.cpp custom-class-def-end
971 \codeline
972 \snippet src_oauth_replyhandlers.cpp custom-class-impl
973
974 \sa autoRefresh, accessTokenAboutToExpire()
975*/
976#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
977void QAbstractOAuth2::refreshTokensImplementation()
978{
979 Q_D(QAbstractOAuth2);
980 qCDebug(d->loggingCategory, "%s class does not support refreshing", metaObject()->className());
981}
982#endif // QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
983
984/*!
985 Destroys the QAbstractOAuth2 instance.
986*/
987QAbstractOAuth2::~QAbstractOAuth2()
988{}
989
990/*!
991 The returned URL is based on \a url, combining it with the given
992 \a parameters and the access token.
993*/
994QUrl QAbstractOAuth2::createAuthenticatedUrl(const QUrl &url, const QVariantMap &parameters)
995{
996 Q_D(const QAbstractOAuth2);
997 if (Q_UNLIKELY(d->token.isEmpty())) {
998 qCWarning(d->loggingCategory, "Empty access token");
999 return QUrl();
1000 }
1001 QUrl ret = url;
1002 QUrlQuery query(ret.query());
1003 query.addQueryItem(key: QtOAuth2RfcKeywords::accessToken, value: d->token);
1004 for (auto it = parameters.begin(), end = parameters.end(); it != end ;++it)
1005 query.addQueryItem(key: it.key(), value: it.value().toString());
1006 ret.setQuery(query);
1007 return ret;
1008}
1009
1010/*!
1011 \deprecated [6.11] Please use QtNetwork classes directly instead, see
1012 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
1013
1014 Sends an authenticated HEAD request and returns a new
1015 QNetworkReply. The \a url and \a parameters are used to create
1016 the request.
1017
1018 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.4}
1019 {Hypertext Transfer Protocol -- HTTP/1.1: HEAD}
1020*/
1021QNetworkReply *QAbstractOAuth2::head(const QUrl &url, const QVariantMap &parameters)
1022{
1023 Q_D(QAbstractOAuth2);
1024 QNetworkReply *reply = d->networkAccessManager()->head(request: d->createRequest(url, parameters: &parameters));
1025 connect(sender: reply, signal: &QNetworkReply::finished, context: this, slot: [this, reply]() { emit finished(reply); });
1026 return reply;
1027}
1028
1029/*!
1030 \deprecated [6.11] Please use QtNetwork classes directly instead, see
1031 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
1032
1033 Sends an authenticated GET request and returns a new
1034 QNetworkReply. The \a url and \a parameters are used to create
1035 the request.
1036
1037 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.3}
1038 {Hypertext Transfer Protocol -- HTTP/1.1: GET}
1039*/
1040QNetworkReply *QAbstractOAuth2::get(const QUrl &url, const QVariantMap &parameters)
1041{
1042 Q_D(QAbstractOAuth2);
1043 QNetworkReply *reply = d->networkAccessManager()->get(request: d->createRequest(url, parameters: &parameters));
1044 connect(sender: reply, signal: &QNetworkReply::finished, context: this, slot: [this, reply]() { emit finished(reply); });
1045 return reply;
1046}
1047
1048/*!
1049 \deprecated [6.11] Please use QtNetwork classes directly instead, see
1050 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
1051
1052 Sends an authenticated POST request and returns a new
1053 QNetworkReply. The \a url and \a parameters are used to create
1054 the request.
1055
1056 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.5}
1057 {Hypertext Transfer Protocol -- HTTP/1.1: POST}
1058*/
1059QNetworkReply *QAbstractOAuth2::post(const QUrl &url, const QVariantMap &parameters)
1060{
1061 Q_D(QAbstractOAuth2);
1062 const auto data = d->convertParameters(parameters);
1063 QT_IGNORE_DEPRECATIONS(return post(url, data);)
1064}
1065
1066/*!
1067 \deprecated [6.11] Please use QtNetwork classes directly instead, see
1068 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
1069
1070 \since 5.10
1071
1072 \overload
1073
1074 Sends an authenticated POST request and returns a new
1075 QNetworkReply. The \a url and \a data are used to create
1076 the request.
1077
1078 \sa post(), {https://tools.ietf.org/html/rfc2616#section-9.6}
1079 {Hypertext Transfer Protocol -- HTTP/1.1: POST}
1080*/
1081QNetworkReply *QAbstractOAuth2::post(const QUrl &url, const QByteArray &data)
1082{
1083 Q_D(QAbstractOAuth2);
1084 QNetworkReply *reply = d->networkAccessManager()->post(request: d->createRequest(url), data);
1085 connect(sender: reply, signal: &QNetworkReply::finished, context: this, slot: [this, reply]() { emit finished(reply); });
1086 return reply;
1087}
1088
1089/*!
1090 \deprecated [6.11] Please use QtNetwork classes directly instead, see
1091 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
1092
1093 \since 5.10
1094
1095 \overload
1096
1097 Sends an authenticated POST request and returns a new
1098 QNetworkReply. The \a url and \a multiPart are used to create
1099 the request.
1100
1101 \sa post(), QHttpMultiPart, {https://tools.ietf.org/html/rfc2616#section-9.6}
1102 {Hypertext Transfer Protocol -- HTTP/1.1: POST}
1103*/
1104QNetworkReply *QAbstractOAuth2::post(const QUrl &url, QHttpMultiPart *multiPart)
1105{
1106 Q_D(QAbstractOAuth2);
1107 QNetworkReply *reply = d->networkAccessManager()->post(request: d->createRequest(url), multiPart);
1108 connect(sender: reply, signal: &QNetworkReply::finished, context: this, slot: [this, reply]() { emit finished(reply); });
1109 return reply;
1110}
1111
1112/*!
1113 \deprecated [6.11] Please use QtNetwork classes directly instead, see
1114 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
1115
1116 Sends an authenticated PUT request and returns a new
1117 QNetworkReply. The \a url and \a parameters are used to create
1118 the request.
1119
1120 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.6}
1121 {Hypertext Transfer Protocol -- HTTP/1.1: PUT}
1122*/
1123QNetworkReply *QAbstractOAuth2::put(const QUrl &url, const QVariantMap &parameters)
1124{
1125 Q_D(QAbstractOAuth2);
1126 const auto data = d->convertParameters(parameters);
1127 QT_IGNORE_DEPRECATIONS(return put(url, data);)
1128}
1129
1130/*!
1131 \deprecated [6.11] Please use QtNetwork classes directly instead, see
1132 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
1133
1134 \since 5.10
1135
1136 \overload
1137
1138 Sends an authenticated PUT request and returns a new
1139 QNetworkReply. The \a url and \a data are used to create
1140 the request.
1141
1142 \sa put(), {https://tools.ietf.org/html/rfc2616#section-9.6}
1143 {Hypertext Transfer Protocol -- HTTP/1.1: PUT}
1144*/
1145QNetworkReply *QAbstractOAuth2::put(const QUrl &url, const QByteArray &data)
1146{
1147 Q_D(QAbstractOAuth2);
1148 QNetworkReply *reply = d->networkAccessManager()->put(request: d->createRequest(url), data);
1149 connect(sender: reply, signal: &QNetworkReply::finished, context: this, slot: std::bind(f: &QAbstractOAuth::finished, args: this, args&: reply));
1150 return reply;
1151}
1152
1153/*!
1154 \deprecated [6.11] Please use QtNetwork classes directly instead, see
1155 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
1156
1157 \since 5.10
1158
1159 \overload
1160
1161 Sends an authenticated PUT request and returns a new
1162 QNetworkReply. The \a url and \a multiPart are used to create
1163 the request.
1164
1165 \sa put(), QHttpMultiPart, {https://tools.ietf.org/html/rfc2616#section-9.6}
1166 {Hypertext Transfer Protocol -- HTTP/1.1: PUT}
1167*/
1168QNetworkReply *QAbstractOAuth2::put(const QUrl &url, QHttpMultiPart *multiPart)
1169{
1170 Q_D(QAbstractOAuth2);
1171 QNetworkReply *reply = d->networkAccessManager()->put(request: d->createRequest(url), multiPart);
1172 connect(sender: reply, signal: &QNetworkReply::finished, context: this, slot: std::bind(f: &QAbstractOAuth::finished, args: this, args&: reply));
1173 return reply;
1174}
1175
1176/*!
1177 \deprecated [6.11] Please use QtNetwork classes directly instead, see
1178 \l {OAuth2 HTTP method alternatives}{HTTP method alternatives}.
1179
1180 Sends an authenticated DELETE request and returns a new
1181 QNetworkReply. The \a url and \a parameters are used to create
1182 the request.
1183
1184 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.7}
1185 {Hypertext Transfer Protocol -- HTTP/1.1: DELETE}
1186*/
1187QNetworkReply *QAbstractOAuth2::deleteResource(const QUrl &url, const QVariantMap &parameters)
1188{
1189 Q_D(QAbstractOAuth2);
1190 QNetworkReply *reply = d->networkAccessManager()->deleteResource(
1191 request: d->createRequest(url, parameters: &parameters));
1192 connect(sender: reply, signal: &QNetworkReply::finished, context: this, slot: [this, reply]() { emit finished(reply); });
1193 return reply;
1194}
1195
1196#ifndef QOAUTH2_NO_LEGACY_SCOPE
1197QString QAbstractOAuth2::scope() const
1198{
1199 Q_D(const QAbstractOAuth2);
1200 return d->legacyScope;
1201}
1202#endif
1203
1204QSet<QByteArray> QAbstractOAuth2::grantedScopeTokens() const
1205{
1206 Q_D(const QAbstractOAuth2);
1207 return d->grantedScopeTokens;
1208}
1209
1210#ifndef QOAUTH2_NO_LEGACY_SCOPE
1211void QAbstractOAuth2::setScope(const QString &scope)
1212{
1213 Q_D(QAbstractOAuth2);
1214 d->legacyScopeWasSetByUser = true;
1215 if (d->legacyScope != scope) {
1216 d->legacyScope = scope;
1217 QT_IGNORE_DEPRECATIONS(Q_EMIT scopeChanged(d->legacyScope);)
1218 }
1219 const QSet<QByteArray> splitScope = d->splitScope(scope: d->legacyScope);
1220 if (d->requestedScopeTokens != splitScope) {
1221 d->requestedScopeTokens = splitScope;
1222 Q_EMIT requestedScopeTokensChanged(tokens: splitScope);
1223 }
1224}
1225#endif
1226
1227QSet<QByteArray> QAbstractOAuth2::requestedScopeTokens() const
1228{
1229 Q_D(const QAbstractOAuth2);
1230 return d->requestedScopeTokens;
1231}
1232
1233void QAbstractOAuth2::setRequestedScopeTokens(const QSet<QByteArray> &tokens)
1234{
1235 Q_D(QAbstractOAuth2);
1236 if (!d->checkRequestedScopeTokensValid(scopeTokens: tokens))
1237 return;
1238#ifndef QOAUTH2_NO_LEGACY_SCOPE
1239 d->legacyScopeWasSetByUser = false;
1240#endif
1241 if (tokens != d->requestedScopeTokens) {
1242 d->requestedScopeTokens = tokens;
1243 Q_EMIT requestedScopeTokensChanged(tokens);
1244 }
1245#ifndef QOAUTH2_NO_LEGACY_SCOPE
1246 QString joinedScope = d->joinedScope(scopeTokens: tokens);
1247 if (joinedScope != d->legacyScope) {
1248 d->legacyScope = std::move(joinedScope);
1249 QT_IGNORE_DEPRECATIONS(Q_EMIT scopeChanged(d->legacyScope);)
1250 }
1251#endif
1252}
1253
1254QString QAbstractOAuth2::userAgent() const
1255{
1256 Q_D(const QAbstractOAuth2);
1257 return d->userAgent;
1258}
1259
1260void QAbstractOAuth2::setUserAgent(const QString &userAgent)
1261{
1262 Q_D(QAbstractOAuth2);
1263 if (d->userAgent != userAgent) {
1264 d->userAgent = userAgent;
1265 Q_EMIT userAgentChanged(userAgent);
1266 }
1267}
1268
1269/*!
1270 Returns the \l {https://tools.ietf.org/html/rfc6749#section-3.1.1}
1271 {response_type} used.
1272*/
1273QString QAbstractOAuth2::responseType() const
1274{
1275 Q_D(const QAbstractOAuth2);
1276 return d->responseType;
1277}
1278
1279QString QAbstractOAuth2::clientIdentifierSharedKey() const
1280{
1281 Q_D(const QAbstractOAuth2);
1282 return d->clientIdentifierSharedKey;
1283}
1284
1285void QAbstractOAuth2::setClientIdentifierSharedKey(const QString &clientIdentifierSharedKey)
1286{
1287 Q_D(QAbstractOAuth2);
1288 if (d->clientIdentifierSharedKey != clientIdentifierSharedKey) {
1289 d->clientIdentifierSharedKey = clientIdentifierSharedKey;
1290 Q_EMIT clientIdentifierSharedKeyChanged(clientIdentifierSharedKey);
1291 }
1292}
1293
1294QString QAbstractOAuth2::state() const
1295{
1296 Q_D(const QAbstractOAuth2);
1297 return d->state;
1298}
1299
1300void QAbstractOAuth2::setState(const QString &state)
1301{
1302 Q_D(QAbstractOAuth2);
1303 // Allowed characters are defined in
1304 // https://datatracker.ietf.org/doc/html/rfc6749#appendix-A.5
1305 // state = 1*VSCHAR
1306 // Where
1307 // VSCHAR = %x20-7E
1308 for (QChar c : state) {
1309 if (c < u'\x20' || c > u'\x7E') {
1310 qCWarning(lcOAuth2Validation, "setState() contains illegal character(s), ignoring");
1311 return;
1312 }
1313 }
1314 if (state != d->state) {
1315 d->state = state;
1316 Q_EMIT stateChanged(state);
1317 }
1318}
1319
1320QDateTime QAbstractOAuth2::expirationAt() const
1321{
1322 Q_D(const QAbstractOAuth2);
1323 return d->expiresAtUtc.toLocalTime();
1324}
1325
1326/*!
1327 \brief Gets the current refresh token.
1328
1329 Refresh tokens usually have longer lifespans than access tokens,
1330 so it makes sense to save them for later use.
1331
1332 Returns the current refresh token or an empty string, if
1333 there is no refresh token available.
1334*/
1335QString QAbstractOAuth2::refreshToken() const
1336{
1337 Q_D(const QAbstractOAuth2);
1338 return d->refreshToken;
1339}
1340
1341/*!
1342 \brief Sets the new refresh token \a refreshToken to be used.
1343
1344 A custom refresh token can be used to refresh the access token via this method and then
1345 the access token can be refreshed via \l refreshTokens().
1346
1347*/
1348void QAbstractOAuth2::setRefreshToken(const QString &refreshToken)
1349{
1350 Q_D(QAbstractOAuth2);
1351 if (d->refreshToken != refreshToken) {
1352 d->refreshToken = refreshToken;
1353 Q_EMIT refreshTokenChanged(refreshToken);
1354 }
1355}
1356
1357std::chrono::seconds QAbstractOAuth2::refreshLeadTime() const
1358{
1359 Q_D(const QAbstractOAuth2);
1360 return d->refreshLeadTime;
1361}
1362
1363void QAbstractOAuth2::setRefreshLeadTime(std::chrono::seconds leadTime)
1364{
1365 Q_D(QAbstractOAuth2);
1366 if (leadTime < 0s) {
1367 qCWarning(d->loggingCategory, "Invalid refresh leadTime");
1368 return;
1369 }
1370 if (d->refreshLeadTime == leadTime)
1371 return;
1372 d->refreshLeadTime = leadTime;
1373 d->updateRefreshTimer(clientSideUpdate: true);
1374 Q_EMIT refreshLeadTimeChanged(leadTime);
1375}
1376
1377bool QAbstractOAuth2::autoRefresh() const
1378{
1379 Q_D(const QAbstractOAuth2);
1380 return d->autoRefresh;
1381}
1382
1383void QAbstractOAuth2::setAutoRefresh(bool enabled)
1384{
1385 Q_D(QAbstractOAuth2);
1386 if (d->autoRefresh == enabled)
1387 return;
1388 d->autoRefresh = enabled;
1389 Q_EMIT autoRefreshChanged(enable: enabled);
1390}
1391
1392QAbstractOAuth2::NonceMode QAbstractOAuth2::nonceMode() const
1393{
1394 Q_D(const QAbstractOAuth2);
1395 return d->nonceMode;
1396}
1397
1398void QAbstractOAuth2::setNonceMode(NonceMode mode)
1399{
1400 Q_D(QAbstractOAuth2);
1401 if (mode == d->nonceMode)
1402 return;
1403 d->nonceMode = mode;
1404 emit nonceModeChanged(mode: d->nonceMode);
1405}
1406
1407QString QAbstractOAuth2::nonce() const
1408{
1409 Q_D(const QAbstractOAuth2);
1410 return d->nonce;
1411}
1412
1413void QAbstractOAuth2::setNonce(const QString &nonce)
1414{
1415 Q_D(QAbstractOAuth2);
1416 if (nonce == d->nonce)
1417 return;
1418 d->nonce = nonce;
1419 emit nonceChanged(nonce: d->nonce);
1420}
1421
1422QString QAbstractOAuth2::idToken() const
1423{
1424 Q_D(const QAbstractOAuth2);
1425 return d->idToken;
1426}
1427
1428QUrl QAbstractOAuth2::tokenUrl() const
1429{
1430 Q_D(const QAbstractOAuth2);
1431 return d->tokenUrl;
1432}
1433
1434void QAbstractOAuth2::setTokenUrl(const QUrl &tokenUrl)
1435{
1436 Q_D(QAbstractOAuth2);
1437 if (d->tokenUrl == tokenUrl)
1438 return;
1439
1440 d->tokenUrl = tokenUrl;
1441 emit tokenUrlChanged(tokenUrl: d->tokenUrl);
1442}
1443
1444#ifndef QT_NO_SSL
1445/*!
1446 \since 6.5
1447
1448 Returns the TLS configuration to be used when establishing a mutual TLS
1449 connection between the client and the Authorization Server.
1450
1451 \sa setSslConfiguration(), sslConfigurationChanged()
1452*/
1453QSslConfiguration QAbstractOAuth2::sslConfiguration() const
1454{
1455 Q_D(const QAbstractOAuth2);
1456 return d->sslConfiguration.value_or(u: QSslConfiguration());
1457}
1458
1459/*!
1460 \since 6.5
1461
1462 Sets the TLS \a configuration to be used when establishing
1463 a mutual TLS connection between the client and the Authorization Server.
1464
1465 \sa sslConfiguration(), sslConfigurationChanged()
1466*/
1467void QAbstractOAuth2::setSslConfiguration(const QSslConfiguration &configuration)
1468{
1469 Q_D(QAbstractOAuth2);
1470 const bool configChanged = !d->sslConfiguration || (*d->sslConfiguration != configuration);
1471 if (configChanged) {
1472 d->sslConfiguration = configuration;
1473 Q_EMIT sslConfigurationChanged(configuration);
1474 }
1475}
1476
1477/*!
1478 \fn void QAbstractOAuth2::sslConfigurationChanged(const QSslConfiguration &configuration)
1479 \since 6.5
1480
1481 The signal is emitted when the TLS configuration has changed.
1482 The \a configuration parameter contains the new TLS configuration.
1483
1484 \sa sslConfiguration(), setSslConfiguration()
1485*/
1486#endif // !QT_NO_SSL
1487
1488QT_END_NAMESPACE
1489
1490#include "moc_qabstractoauth2.cpp"
1491

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